package com.arpa.wms.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.IterUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.arpa.core.web.Result;
import com.arpa.ntocc.common.common.cache.PartyCache;
import com.arpa.ntocc.common.common.cache.PartyGroupCache;
import com.arpa.ntocc.common.common.constant.StrConsts;
import com.arpa.ntocc.common.common.exception.ServiceException;
import com.arpa.ntocc.common.common.util.CommonUtil;
import com.arpa.ntocc.common.common.util.UserUtil;
import com.arpa.oms.domain.entity.Sale;
import com.arpa.wms.cache.CustomerCache;
import com.arpa.wms.cache.GoodsCache;
import com.arpa.wms.cache.LocationCache;
import com.arpa.wms.cache.WarehouseCache;
import com.arpa.wms.common.util.LogisticsUtils;
import com.arpa.wms.common.util.OrderIdGeneratorUtil;
import com.arpa.wms.domain.consts.CommonConst;
import com.arpa.wms.domain.consts.NumConst;
import com.arpa.wms.domain.consts.PropertyConst;
import com.arpa.wms.domain.dto.*;
import com.arpa.wms.domain.entity.*;
import com.arpa.wms.domain.enums.*;
import com.arpa.wms.domain.vo.*;
import com.arpa.wms.mapper.PickingMapper;
import com.arpa.wms.service.*;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.Lists;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.testng.collections.Maps;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

import static com.arpa.wms.domain.consts.NumConst.*;
import static com.arpa.wms.domain.consts.TipConst.*;
import static com.arpa.wms.domain.consts.TipConst.CANCELED;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toList;

/**
 * <p>
 * 拣货单 服务实现类
 * </p>
 *
 * @author LEO
 * @since 2020-08-07
 */
@Service
@Log4j2
public class PickingServiceImpl extends ServiceImpl<PickingMapper, Picking> implements IPickingService {

    private final IPickingItemService iPickingItemService;

    private final IOutboundService iOutboundService;

    private final IOutboundItemService iOutboundItemService;

    private final IDeliveryService iDeliveryService;

    private final IDeliveryItemService iDeliveryItemService;

    @Autowired
    private OutCheckService outCheckService;

    @Autowired
    private WarehouseCache warehouseCache;

    @Autowired
    private LocationCache locationCache;

    @Autowired
    private ILocationService iLocationService;

    @Autowired
    private CustomerCache customerCache;

    @Autowired
    private IDeliveryDistributionService iDeliveryDistributionService;

    @Autowired
    private IInventoryService iInventoryService;

    @Autowired
    private PartyCache partyCache;

    @Autowired
    private IPickingRegisterService iPickingRegisterService;

    @Autowired
    private IContainerService iContainerService;

    @Autowired
    @Lazy
    private IWmsTaskService iWmsTaskService;

    @Autowired
    private IOutboundRegisterService iOutboundRegisterService;

    @Autowired
    private PartyGroupCache partyGroupCache;

    @Autowired
    @Lazy
    private WavePickingConfirmationService wavePickingConfirmationService;

    @Autowired
    @Lazy
    private PickingConfirmationService pickingConfirmationService;

    @Autowired
    @Lazy
    private IPrepackagedService prepackagedService;

    @Resource
    public RedissonClient redissonClient;

    @Autowired
    @Lazy
    private IGoodsPnService goodsPnService;
    @Autowired
    private GoodsCache goodsCache;

    public PickingServiceImpl(IPickingItemService iPickingItemService, IOutboundService iOutboundService,
                              IOutboundItemService iOutboundItemService, IDeliveryService iDeliveryService,
                              IDeliveryItemService iDeliveryItemService) {
        this.iPickingItemService = iPickingItemService;
        this.iOutboundService = iOutboundService;
        this.iOutboundItemService = iOutboundItemService;
        this.iDeliveryService = iDeliveryService;
        this.iDeliveryItemService = iDeliveryItemService;
    }

    /**
     * 保存
     *
     * @param entity
     * @return
     */
    @Override
    @Transactional(rollbackFor = {Exception.class})
    public boolean save(Picking entity) {
        if (StringUtils.isBlank(entity.getCode())) {
            entity.setCode(IdUtil.simpleUUID());
        }
        if (StringUtils.isBlank(entity.getCreatedBy())) {
            entity.setCreatedBy(UserUtil.getCode());
            entity.setCreatedName(partyCache.translate(UserUtil.getCode()));
        }
        return super.save(entity);
    }

    /**
     * 根据code更新实体
     *
     * @param entity
     * @return
     */
    @Override
    @Transactional(rollbackFor = {Exception.class})
    public int updateByCode(Picking entity) {
        if (StringUtils.isBlank(entity.getModifiedBy())) {
            entity.setModifiedBy(UserUtil.getCode());
            entity.setModifiedName(partyCache.translate(UserUtil.getCode()));
        }
        return baseMapper.update(entity, new QueryWrapper<Picking>().lambda().eq(Picking::getCode, entity.getCode()).eq(Picking::getGroupCode,UserUtil.getBranchCode()));
    }

    /**
     * 查询列表
     *
     * @param pickingDTO
     * @return
     */
    @Override
    public List<PickingVO> queryList(PickingDTO pickingDTO) {
        //排序字段名需要驼峰转数据库下划线类型字段名
        if (StringUtils.isNotEmpty(pickingDTO.getSortField())) {
            pickingDTO.setSortField(CommonUtil.camel2Underline(pickingDTO.getSortField()));

            if ("type_text".equals(pickingDTO.getSortField())){
                pickingDTO.setSortField("a.type");
            }
        }
        List<PickingVO> pickingVOList = baseMapper.queryList(pickingDTO);

        // 翻译
        pickingVOList.forEach(e -> {
            e.setTypeText(DeliveryTypeEnum.translate(e.getType()));
            e.setWarehouseName(warehouseCache.translate(e.getWarehouseCode()));
        });
        // 过滤掉计划数量为0的拣货单
        pickingVOList = pickingVOList.stream().filter(e -> e.getPlanQuantity().compareTo(BigDecimal.ZERO) > NUM_ZERO)
                .collect(Collectors.toList());

        return pickingVOList;
    }

    /**
     * 查询合计,包含总数
     *
     * @param pickingDTO
     * @return
     */
    @Override
    public PickingVO queryListSum(PickingDTO pickingDTO) {
        if (IterUtil.isEmpty(pickingDTO.getShipmentCodes())) {
            return null;
        }
        StringBuilder sb = new StringBuilder();
        sb.append("SELECT ");
        pickingDTO.getShipmentCodes().forEach(e -> {
            sb.append("(SELECT count(*) AS total FROM `wms_picking`  WHERE 1=1 and is_deleted = 0 ");
            //状态
            if (ObjectUtil.isNotEmpty(pickingDTO.getStatus())) {
                sb.append(" and `status` ='").append(pickingDTO.getStatus()).append("'");
            }
            //入驻商标识
            sb.append(" and `shipment_code` =  '").append(e).append("'");
            //仓库标识
            if (StrUtil.isNotEmpty(pickingDTO.getWarehouseCode())) {
                sb.append(" and `warehouse_code` ='").append(pickingDTO.getWarehouseCode()).append("'");
            }
            if (StrUtil.isNotEmpty(pickingDTO.getGroupCode())) {
                sb.append(" and `group_code` ='").append(pickingDTO.getGroupCode()).append("'");
            }
            //订单号
            if (StrUtil.isNotEmpty(pickingDTO.getDeliveryCode())) {
                sb.append(" and `delivery_code` = '").append(pickingDTO.getDeliveryCode()).append("'");
            }
            //承运商标识
            if (StrUtil.isNotEmpty(pickingDTO.getCarrierCode())) {
                sb.append(" and `carrier_code` = '").append(pickingDTO.getCarrierCode()).append("'");
            }
            //波次号
            if (StrUtil.isNotEmpty(pickingDTO.getWavePickingCode())) {
                sb.append(" and `wave_picking_code` like'%").append(pickingDTO.getWavePickingCode()).append("%'");
            }
            //拣货单号
            if (StrUtil.isNotEmpty(pickingDTO.getCode())) {
                sb.append(" and `code` like '%").append(pickingDTO.getCode()).append("%'");
            }
            //来源单号
            if (StrUtil.isNotEmpty(pickingDTO.getSourceCode())) {
                sb.append(" and `source_code` ='").append(pickingDTO.getSourceCode()).append("'");
            }
            //拣货单类型
            if (ObjectUtil.isNotEmpty(pickingDTO.getType())) {
                sb.append(" and `type` ='").append(pickingDTO.getType()).append("'");
            }
            //是否下发
            if (ObjectUtil.isNotEmpty(pickingDTO.getIssue())) {
                sb.append(" and `is_issue` = ").append(pickingDTO.getIssue());
            }

            if (StrUtil.isNotEmpty(pickingDTO.getCreatedBy())) {
                sb.append(" and `created_by` = '").append(pickingDTO.getCreatedBy()).append("'");
            }
            if (ObjectUtil.isNotEmpty(pickingDTO.getGmtCreated())) {
                sb.append(" and `gmt_created` ='").append(pickingDTO.getGmtCreated()).append("'");
            }
            if (ObjectUtil.isNotEmpty(pickingDTO.getModifiedBy())) {
                sb.append(" and `modified_by` ='").append(pickingDTO.getModifiedBy()).append("'");
            }
            if (StrUtil.isNotEmpty(pickingDTO.getModifiedName())) {
                sb.append(" and `modified_name` ='").append(pickingDTO.getModifiedName()).append("'");
            }
            if (StrUtil.isNotEmpty(pickingDTO.getCreatedName())) {
                sb.append(" and `created_name` ='").append(pickingDTO.getCreatedName()).append("'");
            }
            if (ObjectUtil.isNotEmpty(pickingDTO.getGmtCreatedStart()) && ObjectUtil.isNotEmpty(pickingDTO.getGmtCreatedEnd())) {
                sb.append(" and `gmt_created` between '").append(pickingDTO.getGmtCreatedStart()).append("' and '").append(pickingDTO.getGmtCreatedEnd()).append("'");
            }
            if (ObjectUtil.isNotEmpty(pickingDTO.getGmtModifiedStart()) && ObjectUtil.isNotEmpty(pickingDTO.getGmtModifiedEnd())) {
                sb.append(" and `gmt_modified` between '").append(pickingDTO.getGmtModifiedStart()).append("' and '").append(pickingDTO.getGmtModifiedEnd()).append("'");
            }
            if (ObjectUtil.isNotEmpty(pickingDTO.getPickingCompletionTimeStart()) && ObjectUtil.isNotEmpty(pickingDTO.getPickingCompletionTimeEnd())) {
                sb.append(" and `picking_completion_time` between '").append(pickingDTO.getPickingCompletionTimeStart()).append("' and '").append(pickingDTO.getPickingCompletionTimeEnd()).append("'");
            }
            if (pickingDTO.getPlanQuantityStart() != null) {
                sb.append(" and `plan_quantity` >= ").append(pickingDTO.getPlanQuantityStart());
            }
            if (pickingDTO.getPlanQuantityEnd() != null) {
                sb.append(" and `plan_quantity` <= ").append(pickingDTO.getPlanQuantityEnd());
            }
            if (pickingDTO.getPickingQuantityStart() != null) {
                sb.append(" and `picking_quantity` >= ").append(pickingDTO.getPickingQuantityStart());
            }
            if (pickingDTO.getPickingQuantityEnd() != null) {
                sb.append(" and `picking_quantity` <= ").append(pickingDTO.getPickingQuantityEnd());
            }
            if (pickingDTO.getNotPickingQuantityStart() != null) {
                sb.append(" and `not_picking_quantity` >= ").append(pickingDTO.getNotPickingQuantityStart());
            }
            if (pickingDTO.getNotPickingQuantityEnd() != null) {
                sb.append(" and `not_picking_quantity` <= ").append(pickingDTO.getNotPickingQuantityEnd());
            }
            if (pickingDTO.getGoodsSortQuantityStart() != null) {
                sb.append(" and `goods_sort_quantity` >= ").append(pickingDTO.getGoodsSortQuantityStart());
            }
            if (pickingDTO.getGoodsSortQuantityEnd() != null) {
                sb.append(" and `goods_sort_quantity` <= ").append(pickingDTO.getGoodsSortQuantityEnd());
            }
            if (StrUtil.isNotEmpty(pickingDTO.getOrderTag())) {
                sb.append(" and `order_tag` = '").append(pickingDTO.getOrderTag()).append("'");
            }
            sb.append(") +");
        });
        sb.replace(sb.length() - 1, sb.length(), "");
        sb.append("AS total ");

        return this.baseMapper.queryListSum(sb.toString());
    }



    /**
     * 查询合计,包含总数
     * @param pickingDTO
     * @return
     */
    @Override
    public PickingVO queryListTotal(PickingDTO pickingDTO){
        return this.baseMapper.queryListTotal(pickingDTO);
    }

    /**
     * 订单的拣货数量为0，说明完全缺货
     *
     * @param wavePickingCode
     * @return
     */
    private void handleWholeStockOutDelivery(String wavePickingCode) {
        Delivery delivery = iDeliveryService.getOne(new QueryWrapper<Delivery>().lambda().eq(Delivery::getGroupCode,UserUtil.getBranchCode()).eq(Delivery::getWavePickingCode, wavePickingCode));
        if (delivery.getPickNum().compareTo(BigDecimal.ZERO) == NUM_ZERO) {
            iDeliveryService.update(new UpdateWrapper<Delivery>().lambda().eq(Delivery::getCode, delivery.getCode()).eq(Delivery::getGroupCode,UserUtil.getBranchCode()).set(Delivery::getPickNum, BigDecimal.ZERO).set(Delivery::getWavePickingCode, StrUtil.EMPTY).set(Delivery::getPickStockOutNum,BigDecimal.ZERO).set(Delivery::getStatus,DeliveryStatusEnum.UN_DISTRIBUTION.getValue()).set(Delivery::getRecheckQuantity,BigDecimal.ZERO).set(Delivery::getRecheckRefundQuantity,BigDecimal.ZERO).set(Delivery::getDistributionNum,BigDecimal.ZERO));
            iDeliveryItemService.update(new UpdateWrapper<DeliveryItem>().lambda().eq(DeliveryItem::getGroupCode,UserUtil.getBranchCode()).eq(DeliveryItem::getDeliveryCode, delivery.getCode()).set(DeliveryItem::getPickingQuantity, BigDecimal.ZERO).set(DeliveryItem::getPickStockOutNum, BigDecimal.ZERO).set(DeliveryItem::getWavePickingCode,StrUtil.EMPTY).set(DeliveryItem::getDistributionQuantity,BigDecimal.ZERO).set(DeliveryItem::getStatus,DeliveryStatusEnum.ADD.getValue()).set(DeliveryItem::getRecheckQuantity,BigDecimal.ZERO).set(DeliveryItem::getDistributionQuantity,BigDecimal.ZERO));
            iDeliveryDistributionService.remove(new QueryWrapper<DeliveryDistribution>().lambda().eq(DeliveryDistribution::getGroupCode,UserUtil.getBranchCode()).eq(DeliveryDistribution::getDeliveryCode, delivery.getCode()));
        }
    }
    /**
     * @description 整单上架确认时更新明细中的缺货信息，同时返还分配的库存，并锁定
     * @author xuyang
     * @date 2020/10/23 9:39
     * @param itemRealQuantity :
     **/
    @Override
    @Transactional(rollbackFor = {Exception.class})
    public Result updateItemForCheck(Picking picking, Map<PickingItem, BigDecimal> itemRealQuantity) {
        // 遍历，如果明细分配数量大于实际拣货量则缺货；如果小于则说明拣多了
        PickingItem item;
        BigDecimal pickQuantity;
        for (Map.Entry<PickingItem, BigDecimal> mapItem : itemRealQuantity.entrySet()) {
            item = mapItem.getKey();
            pickQuantity = mapItem.getValue();
            // 获取差异数量 = 分配数量 - 缺货登记数量 - （实际+新增拣货数量）
            BigDecimal diffQuantity = item.getDistributionQuantity().subtract(item.getStockoutQuantity()).subtract(pickQuantity);
            if (diffQuantity.compareTo(BigDecimal.ZERO) < 0) {
                throw new ServiceException("拣货量与缺货数量总和超过了实际分配量，请确认！");
            } else if (diffQuantity.compareTo(BigDecimal.ZERO) > 0) {
                // 缺货登记返还库存
                iInventoryService.stockoutInventory(item.getInventoryCode(), diffQuantity, StockoutTypeEnum.PICKING.getValue(), item.getPickingCode());
            }

            // 更新缺货数量(分配数量-实际拣货数量）、更新拣货数量、状态
            iPickingItemService.update(new UpdateWrapper<PickingItem>().lambda().eq(PickingItem::getCode, item.getCode())
                    .eq(PickingItem::getGroupCode,UserUtil.getBranchCode())
                    .set(PickingItem::getPickingQuantity, pickQuantity)
                    .set(PickingItem::getStatus, PickingStatusEnum.FINISH_PICK.getValue())
                    .setSql(" stockout_quantity = distribution_quantity -" + pickQuantity));
        }

        return Result.ok();
    }


    /**
     * @param pickingVO:
     * @description 获取明细及实际的拣货数量
     * @author xuyang
     * @date 2020/10/23 9:12
     **/
    @Transactional(rollbackFor = {Exception.class})
    public Map<PickingItem, BigDecimal> calcRealPickingQuantity(PickingVO pickingVO) {
        List<PickingItemWithRegisterVO> itemWithRegisterVOS = pickingVO.getPickingItemWithRegisterVOList();
        // 根据状态过滤，获取未保存的数据
        List<PickingRegister> unSaveRegisters = itemWithRegisterVOS.stream().filter(e -> e.getStatus().equals(PickingRegisterStatusEnum.NOT_PICK.getValue()))
                .map(e -> {
                    if (e.getPickingQuantity() == null || BigDecimal.ZERO.compareTo(e.getPickingQuantity()) > 0) {
                        throw new ServiceException("请填写正确的拣货数量");
                    }
                    PickingRegister register = new PickingRegister();
                    BeanUtils.copyProperties(e, register);
                    return register;
                }).collect(Collectors.toList());
        // 数据库中的最新的已拣货数据
        List<PickingRegister> allRegisters = iPickingRegisterService.list(new QueryWrapper<PickingRegister>().lambda()
                .eq(PickingRegister::getGroupCode,UserUtil.getBranchCode())
                .eq(PickingRegister::getPickingCode, pickingVO.getCode())
                .eq(PickingRegister::getStatus, PickingRegisterStatusEnum.FINISH_PICKING.getValue())
                .eq(PickingRegister::getDeleted, 0));
        // 将新的待保存的数据合并
        allRegisters.addAll(unSaveRegisters);

        List<PickingItem> pickingItems = iPickingItemService.list(new QueryWrapper<PickingItem>().lambda()
                .eq(PickingItem::getGroupCode,UserUtil.getBranchCode())
                .eq(PickingItem::getPickingCode, pickingVO.getCode()).eq(PickingItem::getDeleted, 0));
        // 根据确认单统计各明细实际拣货数量
        Map<String, BigDecimal> registerQuantity = new HashMap<>();
        allRegisters.forEach(e -> {
            registerQuantity.put(e.getPickingItemCode(), registerQuantity.getOrDefault(e.getPickingItemCode(), BigDecimal.ZERO).add(e.getPickingQuantity()));
        });
        // 拼接结果，拣货明细 -- 实际拣货量
        Map<PickingItem, BigDecimal> itemWithRealQuantity;
        itemWithRealQuantity = pickingItems.stream().collect(Collectors.toMap(e -> e, e -> registerQuantity.getOrDefault(e.getCode(), BigDecimal.ZERO)));

        return itemWithRealQuantity;
    }

    /**
     * @param pickingItemWithRegisterVOS:
     * @description PC端保存新维护的确认数据
     * @author xuyang
     * @date 2020/10/23 8:27
     **/
    @Transactional(rollbackFor = {Exception.class})
    public Result savePCUnSaveRegister(List<PickingItemWithRegisterVO> pickingItemWithRegisterVOS,String wavePickingCode) {
        String pickingCode = pickingItemWithRegisterVOS.get(0).getPickingCode();
        WmsTask task = iWmsTaskService.getOne(new QueryWrapper<WmsTask>().lambda().eq(WmsTask::getSourceCode, pickingCode)
        .eq(WmsTask::getGroupCode,UserUtil.getBranchCode()));

        List<PickingRegister> unSaveRegisters = new ArrayList<>();
        // PC前端只可维护 未拣货状态 的数据，保存时也只保存拣货数量大于0的数据
        List<PickingItemWithRegisterVO> unSaveItemWithRegisters = pickingItemWithRegisterVOS.stream()
                .filter(e -> {
                    boolean flag1 = PickingRegisterStatusEnum.NOT_PICK.getValue().equals(e.getStatus());
                    BigDecimal pickingQuantity = ObjectUtil.defaultIfNull(e.getPickingQuantity(), BigDecimal.ZERO);
                    boolean flag2 = pickingQuantity.compareTo(BigDecimal.ZERO) > 0;
                    return flag1 && flag2;
                }).collect(Collectors.toList());

        if (!unSaveItemWithRegisters.isEmpty()) {
            unSaveItemWithRegisters.forEach(e -> {
                PickingRegister pickingRegister = new PickingRegister();
                BeanUtil.copyProperties(e, pickingRegister);
                pickingRegister.setStatus(PickingRegisterStatusEnum.FINISH_PICKING.getValue());
                pickingRegister.setCode(IdUtil.simpleUUID());
                pickingRegister.setCreatedBy(UserUtil.getCode());
                pickingRegister.setCreatedName(partyCache.translate(UserUtil.getCode()));
                pickingRegister.setModifiedBy(StrUtil.EMPTY);
                pickingRegister.setModifiedName(StrUtil.EMPTY);
                pickingRegister.setWavePickingCode(wavePickingCode);
                pickingRegister.setGroupCode(UserUtil.getBranchCode());
                if (task != null) {
                    pickingRegister.setTaskCode(task.getCode());
                    pickingRegister.setOperatorBy(task.getOperatorBy());
                    pickingRegister.setOperatorName(task.getOperatorName());
                }
                unSaveRegisters.add(pickingRegister);
            });
            iPickingRegisterService.saveBatch(unSaveRegisters);
        }
        return Result.ok();
    }

    /**
     * 更新库位上的动碰次数
     *
     * @param pickingItem
     */
    @Transactional(rollbackFor = {Exception.class})
    public void updateLocationActionNum(List<PickingItem> pickingItem) {
        pickingItem.forEach(e -> {
            iLocationService.updateActionNum(e.getLocation(), 1);
        });
    }

    @Override
    public PickingVO getVoByCode(String code) {
        PickingVO pickingVO = new PickingVO();
        Picking entity = this.getOne(new QueryWrapper<Picking>().lambda().eq(Picking::getCode, code).eq(Picking::getGroupCode,UserUtil.getBranchCode()));
        BeanUtil.copyProperties(entity, pickingVO);

        PickingItemDTO pickingItemDTO = new PickingItemDTO();
        pickingItemDTO.setPickingCode(entity.getCode());
        List<PickingItemVO> pickingItemVOS = iPickingItemService.queryList(pickingItemDTO);

        // 翻译
        pickingVO.setWarehouseName(warehouseCache.translate(pickingVO.getWarehouseCode()));
        pickingVO.setCustomerName(customerCache.translate(pickingVO.getCustomerCode()));

        pickingItemVOS.stream().forEach(e -> {
            e.setLocationName(locationCache.translate(e.getLocation()));
        });
        pickingVO.setPickingItemVOList(pickingItemVOS);


        return pickingVO;
    }

    @Override
    public PickingVO getVoByCodeWithAllItem(String code,String groupCode) {
        PickingVO pickingVO = new PickingVO();
        Picking entity = this.getOne(new QueryWrapper<Picking>().lambda().eq(Picking::getCode, code).eq(Picking::getGroupCode,groupCode));
        BeanUtil.copyProperties(entity, pickingVO);

        List<PickingItem> pickingItems = iPickingItemService.list(new QueryWrapper<PickingItem>().lambda()
                .eq(PickingItem::getGroupCode,groupCode)
                .eq(PickingItem::getPickingCode, entity.getCode()));
        List<PickingItemVO> vos = pickingItems.stream().map(e->{
            PickingItemVO vo = new PickingItemVO();
            BeanUtil.copyProperties(e, vo, false);
            return vo;
        }).collect(Collectors.toList());

        // 翻译
        pickingVO.setWarehouseName(warehouseCache.translate(pickingVO.getWarehouseCode()));
        pickingVO.setCustomerName(customerCache.translate(pickingVO.getCustomerCode()));

        vos.stream().forEach(e -> {
            e.setLocationName(locationCache.translate(e.getLocation()));
        });
        pickingVO.setPickingItemVOList(vos);


        return pickingVO;
    }

    @Override
    public PickingVO getVoWithRegisterByCode(String code) {
        String groupCode = UserUtil.getBranchCode();
        PickingVO pickingVO = new PickingVO();
        Picking entity = this.getOne(new QueryWrapper<Picking>().lambda().eq(Picking::getCode, code).eq(Picking::getGroupCode,groupCode));
        BeanUtil.copyProperties(entity, pickingVO);
        int count = iDeliveryService.count(new LambdaQueryWrapper<Delivery>()
                .eq(Delivery::getWavePickingCode,entity.getWavePickingCode()).eq(Delivery::getIsCancel, NUM_ZERO)
                .eq(Delivery::getGroupCode,groupCode));
        pickingVO.setOrderQuantity(BigDecimal.valueOf(count));

        WmsTask wmsTask = iWmsTaskService.getOne(new LambdaQueryWrapper<WmsTask>().eq(WmsTask::getGroupCode,groupCode).eq(WmsTask::getSourceCode,entity.getCode()).eq(WmsTask::getGroupCode,groupCode).last("limit 1"));
        if(null!=wmsTask){
            String name = wmsTask.getOperatorName();
            pickingVO.setOperatorName(name);
        }
        PickingItemDTO pickingItemDTO = new PickingItemDTO();
        pickingItemDTO.setPickingCode(entity.getCode());
        pickingItemDTO.setGroupCode(groupCode);
        List<PickingItemWithRegisterVO> pickingItemWithRegisterVOS = iPickingItemService.queryItemWithRegisterList(pickingItemDTO);
        pickingItemWithRegisterVOS = pickingItemWithRegisterVOS.stream().filter(e ->
                e.getDistributionQuantity() != null &&  e.getDistributionQuantity().compareTo(BigDecimal.ZERO) > NUM_ZERO).collect(Collectors.toList());
        // 翻译
        pickingVO.setWarehouseName(warehouseCache.translate(pickingVO.getWarehouseCode()));
        pickingVO.setCustomerName(customerCache.translate(pickingVO.getCustomerCode()));
        pickingVO.setGroupName(partyGroupCache.translate(pickingVO.getGroupCode()));
        pickingVO.setLogisticsName(LogisticsUtils.translate(pickingVO.getLogisticsCode()));
        pickingItemWithRegisterVOS.stream().forEach(e -> {
            e.setLocationName(locationCache.translate(e.getLocation()));
            e.setStatusText(PickingRegisterStatusEnum.translate(e.getStatus()));
            e.setTotalPlanQuantity(e.getPlanQuantity());
        });
       /* //将相同商品的计划数量进行合并，便于前台展示
        Map<String, List<PickingItemWithRegisterVO>> listMap = pickingItemWithRegisterVOS.stream().collect(Collectors.groupingBy(PickingItemWithRegisterVO::getGoodsCode));
        for (Map.Entry<String,List<PickingItemWithRegisterVO>> listEntry : listMap.entrySet()) {
            if (listEntry.getValue().size() > NUM_ONE) {
                BigDecimal planQuantity = listEntry.getValue().stream().map(PickingItemWithRegisterVO::getPlanQuantity).reduce(BigDecimal.ZERO, BigDecimal::add);
                pickingItemWithRegisterVOS.stream().forEach(e -> {
                    if (e.getGoodsCode().equals(listEntry.getKey())) {
                        e.setTotalPlanQuantity(planQuantity);
                    }
                });
            }
        }*/
        //按照库位编码进行排序
        List<PickingItemWithRegisterVO> sortedItem = pickingItemWithRegisterVOS.stream().sorted(Comparator.comparing(PickingItemWithRegisterVO::getLocationName)
                .thenComparing(PickingItemWithRegisterVO::getGoodCode))
                .collect(Collectors.toList());
        pickingVO.setPickingItemWithRegisterVOList(sortedItem);
        pickingVO.setTypeText(DeliveryTypeEnum.translate(pickingVO.getType()));
        //添加包裹数
        if (StrUtil.isNotEmpty(entity.getPrepackagedCode())) {
            Prepackaged prepackaged = prepackagedService.getOne(new LambdaQueryWrapper<Prepackaged>()
                    .eq(Prepackaged::getGroupCode,groupCode)
                    .select(Prepackaged::getDistributionPackQuantity).eq(Prepackaged::getCode, entity.getPrepackagedCode()));
            pickingVO.setPlanPackQuantity(prepackaged.getDistributionPackQuantity());
        }

        return pickingVO;
    }

    @Override
    public PickingVO getRegisterByCodeForPrint(String code) {
        String groupCode = UserUtil.getBranchCode();
        PickingVO pickingVO = new PickingVO();
        Picking entity = this.getOne(new QueryWrapper<Picking>().lambda().eq(Picking::getCode, code).eq(Picking::getGroupCode,groupCode));
        BeanUtil.copyProperties(entity, pickingVO);

        PickingItemDTO pickingItemDTO = new PickingItemDTO();
        pickingItemDTO.setPickingCode(entity.getCode());
        pickingItemDTO.setGroupCode(groupCode);
//        List<PickingItemWithRegisterVO> pickingItemWithRegisterVOS = iPickingItemService.queryItemWithRegisterList(pickingItemDTO);
        //此方法只是单表查询wms_picking_item返回PickingItemWithRegisterVO类
        List<PickingItemWithRegisterVO> pickingItemWithRegisterVOS = iPickingItemService.queryItemReturnRegisterList(pickingItemDTO);

        // 翻译
        pickingVO.setWarehouseName(warehouseCache.translate(pickingVO.getWarehouseCode()));
        pickingVO.setCustomerName(customerCache.translate(pickingVO.getCustomerCode()));
        pickingVO.setGroupName(partyGroupCache.translate(pickingVO.getGroupCode()));

        //合并同一商品在相同库位不同库存的商品
        pickingItemWithRegisterVOS = combineGoodsInfoOfSameLocation(pickingItemWithRegisterVOS);
        //处理取消订单
        handleCanceldDelivery(pickingVO,pickingItemWithRegisterVOS );
        //过滤掉完全取消的商品
        pickingItemWithRegisterVOS = pickingItemWithRegisterVOS.stream().filter(e -> e.getDistributionQuantity().compareTo(BigDecimal.ZERO) > 0).collect(Collectors.toList());

        pickingItemWithRegisterVOS.stream().forEach(e -> {
            e.setLocationName(locationCache.translate(e.getLocation()));
            e.setStatusText(PickingRegisterStatusEnum.translate(e.getStatus()));
        });
        //对库位进行升序排序
        pickingItemWithRegisterVOS = pickingItemWithRegisterVOS.stream()
                .sorted(Comparator.comparing(PickingItemWithRegisterVO::getLocationName)).collect(toList());
        pickingVO.setPickingItemWithRegisterVOList(pickingItemWithRegisterVOS);

        return pickingVO;
    }

    /**
     * 处理含有取消订单的拣货单信息
     *
     * @param pickingVO
     * @param pickingItemWithRegisterVOS
     * @return
     */
    private void handleCanceldDelivery(PickingVO pickingVO,List<PickingItemWithRegisterVO> pickingItemWithRegisterVOS) {
        String wavePickingCode = pickingVO.getWavePickingCode();
        String groupCode = pickingVO.getGroupCode();
        //判断当前波次下是否有取消订单，如果有的话，需要对拣货主单和明细的一些数量重新赋值
        int cancelDeliveryCount = iDeliveryService.count(new LambdaQueryWrapper<Delivery>().select(Delivery::getCode)
                .eq(Delivery::getWavePickingCode, wavePickingCode)
                .eq(Delivery::getGroupCode, groupCode)
                .eq(Delivery::getIsCancel, NUM_ONE));

        if (cancelDeliveryCount > 0) {
            //判断是否全部取消
            List<String> notCancelDeliveryCode = iDeliveryService.list(new LambdaQueryWrapper<Delivery>().select(Delivery::getCode)
                    .eq(Delivery::getWavePickingCode, wavePickingCode)
                    .eq(Delivery::getGroupCode, groupCode)
                    .eq(Delivery::getIsCancel, NUM_ZERO)).stream().map(Delivery::getCode).collect(Collectors.toList());
            if (notCancelDeliveryCode.size() == 0) {
                pickingVO.setGoodsSortQuantity(BigDecimal.ZERO);
                pickingVO.setPlanQuantity(BigDecimal.ZERO);
                return ;
            }

            List<DeliveryDistribution> distributionList = iDeliveryDistributionService.list(new LambdaQueryWrapper<DeliveryDistribution>()
                    .in(DeliveryDistribution::getDeliveryCode, notCancelDeliveryCode)
                    .eq(DeliveryDistribution::getGroupCode, groupCode)
            );
            //用分配明细的商品统计商品数量，以防订单有取消，显示商品种类数量不准
            Map<String,List<DeliveryDistribution>> listGoodsMap = distributionList.stream().collect(Collectors.groupingBy(r -> r.getLocation() + "_" + r.getGoodCode()));
            int goodsSortQuantity = listGoodsMap.keySet().size();
            BigDecimal totalPlanQuantity = distributionList.stream().map(DeliveryDistribution::getDistributionQuantity).reduce(BigDecimal.ZERO, BigDecimal::add);
            pickingVO.setGoodsSortQuantity(new BigDecimal(goodsSortQuantity));
            pickingVO.setPlanQuantity(totalPlanQuantity);
            pickingItemWithRegisterVOS.stream().forEach(e -> {
                List<DeliveryDistribution> deliveryDistributionVOS = MapUtil.get(listGoodsMap, e.getLocation() + "_" + e.getGoodCode(), List.class);
                if (IterUtil.isNotEmpty(deliveryDistributionVOS)) {
                    BigDecimal itemPlanQuantity = deliveryDistributionVOS.stream().map(DeliveryDistribution::getDistributionQuantity).reduce(BigDecimal.ZERO, BigDecimal::add);
                    e.setDistributionQuantity(itemPlanQuantity);
                    BigDecimal itemPickingQuantity = deliveryDistributionVOS.stream().map(DeliveryDistribution::getPickingQuantity).reduce(BigDecimal.ZERO, BigDecimal::add);
                    e.setItemPickingQuantity(itemPickingQuantity);
                } else {
                    e.setDistributionQuantity(BigDecimal.ZERO);
                }
            });

        }
    }

    /**
     * 根据拣货单生成出库单信息
     *
     * @param pickingCode
     * @return
     */
    @Transactional(rollbackFor = {Exception.class})
    public Result generateOutboundInfo(String pickingCode) {
        String groupCode = UserUtil.getBranchCode();
        Picking picking = getOne(new QueryWrapper<Picking>().lambda().eq(Picking::getCode, pickingCode).eq(Picking::getGroupCode,groupCode));
        log.info("拣货完成生成出库单。拣货单：{}", JSONUtil.toJsonStr(picking));
        // 根据拣货确认单生成出库单明细
        //PickingRegisterDTO pickingRegisterDTO = new PickingRegisterDTO();
        //pickingRegisterDTO.setPickingCode(pickingCode);
        //pickingRegisterDTO.setDeleted(0);
        //pickingRegisterDTO.setStatus(PickingRegisterStatusEnum.FINISH_PICKING.getValue());
        //pickingRegisterDTO.setPageSize(Integer.MAX_VALUE);
        //List<PickingRegisterVO> pickingRegisters = iPickingRegisterService.queryList(pickingRegisterDTO);
        List<PickingRegister> pickingRegisters = iPickingRegisterService.list(new LambdaQueryWrapper<PickingRegister>()
                .eq(PickingRegister::getPickingCode,pickingCode)
                .eq(PickingRegister::getGroupCode,groupCode)
                .eq(PickingRegister::getStatus,PickingRegisterStatusEnum.FINISH_PICKING.getValue()));
        log.info("获取拣货单拣货登记明细：{}", JSONUtil.toJsonStr(pickingRegisters));
        pickingRegisters = pickingRegisters.stream().filter(e -> e.getDistributionQuantity().compareTo(BigDecimal.ZERO) > NUM_ZERO).collect(Collectors.toList());
        // 如果拣货确认单为空，即所有商品都缺货登记了，那就不生成出库单
        if (pickingRegisters.isEmpty()) {
            //if (picking.getIsOmsOrder().equals(NUM_ONE)) {
            //    // XYTODO TODO 锁库存操作即将不用，也就没有释放被锁库存操作
            //    iSaleService.releaseUseQuantity(picking.getSourceCode());
            //}
            // 拣货数量全为0，则不生成出库单，订单状态要改为待分配
            iDeliveryService.update(new UpdateWrapper<Delivery>().lambda()
                    .eq(Delivery::getGroupCode,groupCode)
                    .eq(Delivery::getWavePickingCode, picking.getWavePickingCode())
                .set(Delivery::getStatus, DeliveryStatusEnum.UN_DISTRIBUTION.getValue()));

        } else {

            // 生成出库单
            Outbound outbound = new Outbound();
            BeanUtil.copyProperties(picking, outbound);
            // 生成出库单号  CK+货主编码+yyyyMMdd+四位流水号
            outbound.setCode(OrderIdGeneratorUtil.generatorOrderId(OrderIdGeneratorUtil.OrderTypeEnum.CK, picking.getShipmentCode(),groupCode));
            outbound.setPickingCode(pickingCode);
            outbound.setDeleted(0);
            // 计划发货数量 = 拣货数量
            outbound.setPlanQuantity(picking.getPickingQuantity());
            outbound.setIssue(0);
            if(picking.getReview()==0){
                log.info("拣货单无需复核，出库单直接更改为待出库，复核数量为拣货数量：出库单信息{}",JSONUtil.toJsonStr(outbound));
                //拣货单不需要复核，如果不复核直接创建复核单
                outbound.setStatus(OutboundStatusEnum.NOT_OUT.getValue());
                outbound.setRecheckQuantity(picking.getPickingQuantity());
            }else{
                outbound.setStatus(OutboundStatusEnum.NOT_RECHECK.getValue());
            }

            // 出库单中订单数量
            int deliveryCount;
            if (OrderTagEnum.QUANTITY.getValue().equals(picking.getOrderTag())) {
                deliveryCount = NUM_ONE;
            } else {
                deliveryCount = iDeliveryService.count(new QueryWrapper<Delivery>().lambda().eq(Delivery::getGroupCode,groupCode).eq(Delivery::getWavePickingCode, picking.getWavePickingCode()));
            }
            outbound.setDeliveryCount(deliveryCount);
            if (picking.getStockoutQuantity().compareTo(BigDecimal.ZERO)> NUM_ZERO) {
                //重新计算出库单的商品种数（缺货登记的时候会影响商品种数）
                //List<DeliveryItem> deliveryItemList = iDeliveryItemService.list(new UpdateWrapper<DeliveryItem>().lambda()
                //        .eq(DeliveryItem::getWavePickingCode, picking.getWavePickingCode())
                //        .eq(DeliveryItem::getDeleted, NUM_ZERO));
                //int size = deliveryItemList.stream().collect(Collectors.groupingBy(DeliveryItem::getGoodCode)).size();
                BigDecimal goodsTotalNum = iDeliveryService.getGoodsTotalNumByWavePickingCode(picking.getWavePickingCode());
                outbound.setGoodsSortQuantity(goodsTotalNum);
            }
            //出库单中订单计划总数量
            BigDecimal deliveryPlanQuantity = iDeliveryService.getTotalNumByWavePickingCode(picking.getWavePickingCode());
            //List<Delivery> deliveryList = iDeliveryService.list(new LambdaQueryWrapper<Delivery>().select(Delivery::getTotalNum)
            //        .eq(Delivery::getWavePickingCode, picking.getWavePickingCode()));
            // deliveryPlanQuantity = deliveryList.stream().map(Delivery::getTotalNum).reduce(BigDecimal.ZERO, BigDecimal::add);
            outbound.setDeliveryPlanQuantity(deliveryPlanQuantity);
            // 保存出库单
            iOutboundService.save(outbound);

            log.info("保存出库单信息：{}", JSONUtil.toJsonStr(outbound));
            // 生成出库单明细、待复核出库确认单
            for (PickingRegister pickingRegister : pickingRegisters) {
                saveOutboundItemAndRegister(pickingRegister, outbound,picking.getReview());
            }

            //生成拣货单不需要复核，直接调用出库确认
            if(picking.getReview()==0) {
                //更新订单复核数量为拣货数量，
                //拣货数量与计划数量不一致，订单状态为待复核
                //一致为待出库
               int count = iDeliveryService.updateStatusWithoutRecheck(picking.getWavePickingCode());
                log.info("拣货单无需复核，更新订单复核数量为拣货数量，波次号{},更新结果:{}",picking.getWavePickingCode(),count);
                //更新订单未待出库状态；
                //outCheckService.outCheck(outbound,true);

                log.info("出库确认：【step1】，处理作废单");
                outCheckService.handleInvalid(outbound);
                outCheckService.confirmDelivery(outbound,true);
                outCheckService.outConfirm(outbound.getCode(), outbound.getWavePickingCode(),groupCode);
            } else {
                // 出库单需要复核，则订单状态更新为“待复核”
                int count = iDeliveryService.updateStatusWithRecheck(picking.getWavePickingCode(),groupCode);
                log.info("拣货单需复核，更新订单复核数量为拣货数量，波次号{},更新结果:{}",picking.getWavePickingCode(),count);
            }

        }
        return Result.ok();
    }

    /**
     * 保存出库单明细、待复核的出库确认单
     *
     * @param pickingRegister
     * @param outbound
     */
    @Transactional(rollbackFor = {Exception.class})
    public void saveOutboundItemAndRegister(PickingRegister pickingRegister, Outbound outbound,int isReview) {
        OutboundItem outboundItem = new OutboundItem();
        // 复制属性
        BeanUtil.copyProperties(pickingRegister, outboundItem);
        outboundItem.setOutboundCode(outbound.getCode());
        outboundItem.setPickingRegisterCode(pickingRegister.getCode());
        outboundItem.setPlanQuantity(pickingRegister.getPickingQuantity());
        if(isReview==0) {
            outboundItem.setRecheckQuantity(pickingRegister.getPickingQuantity());
            outboundItem.setStatus(OutboundRecheckStatusEnum.RECHECKED.getValue());
        }else{
            outboundItem.setStatus(OutboundRecheckStatusEnum.NOT_RECHECK.getValue());
        }
        // 出库数量初始值为0
        outboundItem.setOutboundQuantity(BigDecimal.ZERO);
        outboundItem.setDeleted(0);
        outboundItem.setCode(StrUtil.EMPTY);
        outboundItem.setTaskCode(StrUtil.EMPTY);
        outboundItem.setOperatorBy(StrUtil.EMPTY);
        outboundItem.setOperatorName(StrUtil.EMPTY);
        outboundItem.setCreatedBy(StrUtil.EMPTY);
        iOutboundItemService.save(outboundItem);

        // 生成一条待复核的出库复核单
        OutboundRegister outboundRegister = new OutboundRegister();
        // 复制属性
        BeanUtil.copyProperties(pickingRegister, outboundRegister);
        outboundRegister.setOutboundCode(outbound.getCode());
        outboundRegister.setOutboundItemCode(outboundItem.getCode());
        outboundRegister.setPickingRegisterCode(pickingRegister.getCode());
        outboundRegister.setPlanQuantity(pickingRegister.getPickingQuantity());

        if(isReview==0) {
            // 复核数量初始值为总待拣数量
            outboundRegister.setRecheckQuantity(pickingRegister.getPickingQuantity());
            outboundRegister.setNotRecheckQuantity(BigDecimal.ZERO);
            outboundRegister.setStatus(OutboundRecheckStatusEnum.RECHECKED.getValue());
        }else{
            outboundRegister.setNotRecheckQuantity(pickingRegister.getPickingQuantity());
            outboundRegister.setStatus(OutboundRecheckStatusEnum.NOT_RECHECK.getValue());
        }
        outboundRegister.setDeleted(0);
        outboundRegister.setCode(StrUtil.EMPTY);
        outboundRegister.setTaskCode(StrUtil.EMPTY);
        outboundRegister.setOperatorBy(StrUtil.EMPTY);
        outboundRegister.setOperatorName(StrUtil.EMPTY);
        outboundRegister.setCreatedBy(StrUtil.EMPTY);
        outboundRegister.setWavePickingCode(outboundItem.getWavePickingCode());
        iOutboundRegisterService.save(outboundRegister);
    }


    /**
     * 更新拣货单下发状态
     *
     * @param code 拣货单标识
     */
    @Override
    @Transactional(rollbackFor = {Exception.class})
    public void updateIssueStatus(String code, String groupCode) {
        this.update(new UpdateWrapper<Picking>().lambda().eq(Picking::getCode, code)
                .eq(Picking::getGroupCode,groupCode)
                .set(Picking::getIssue, IssueStatusEnum.ISSUED));
    }

    @Override
    public PickingTaskDetailVO getTaskDetailByCode(PickingDTO pickingDTO) {
        return baseMapper.getTaskDetailByCode(pickingDTO);
    }

    /**
     * @param pickingCode          :
     * @param pickingItemCode      :
     * @param unFinishRegisterCode :
     * @param pickingQuantity      :
     * @param containerBarCode     :
     * @description PDA拣货确认（针对单个明细确认）
     * @author xuyang
     * @date 2020/10/26 8:41
     **/
    @Override
    @Transactional(rollbackFor = {Exception.class})
    public PdaSwitchPageVO singleCheck(String pickingCode, String pickingItemCode, String unFinishRegisterCode,
                                       BigDecimal pickingQuantity, String containerBarCode,
                                       boolean isBatch,String goodsBarCode,String pn) {
        String groupCode = UserUtil.getBranchCode();
        //pn码校验
        validatePnInfo(pickingCode,goodsBarCode,pn,groupCode);
        PdaSwitchPageVO switchPageVO = new PdaSwitchPageVO();
        // 默认PDA不跳转
        switchPageVO.setToPage(NumConst.NUM_ZERO);
        switchPageVO.setMsg("操作成功");

        // 判断明细是否已完成
        PickingItem item = iPickingItemService.getOne(new QueryWrapper<PickingItem>().lambda().eq(PickingItem::getCode, pickingItemCode).eq(PickingItem::getGroupCode,groupCode));

        log.info("PDA批量拣货确认，获取拣货明细，{}",JSONUtil.toJsonStr(item));
        if (PickingRegisterStatusEnum.FINISH_PICKING.getValue().equals(item.getStatus())) {
            throw new ServiceException("已拣货完成，无法继续拣货");
        }
        String wavePickingCode = item.getWavePickingCode();
        String oldInventoryCode = item.getOldInventoryCode();

        PickingRegister pickingRegister = iPickingRegisterService.getOne(new QueryWrapper<PickingRegister>().lambda().eq(PickingRegister::getCode, unFinishRegisterCode).eq(PickingRegister::getGroupCode,groupCode));
        pickingRegister.setPickingQuantity(pickingQuantity);
        pickingRegister.setContainerBarCode(containerBarCode);

        log.info("PDA批量拣货确认，获取拣货登记信息，{}",JSONUtil.toJsonStr(pickingRegister));
        // 更新任务状态
        iWmsTaskService.updateTaskWorkingBySourceCode(pickingRegister.getPickingCode());

        // 校验拣货数量
        if (pickingRegister.getPickingQuantity() == null || BigDecimal.ZERO.compareTo(pickingRegister.getPickingQuantity()) >= 0) {
            log.error("PDA批量拣货确认，拣货数量必须大于0，拣货数量: {}",pickingRegister.getPickingQuantity());
            throw new ServiceException("拣货数量必须大于0");
        }
        // 检查主单状态
        Picking picking = getOne(new QueryWrapper<Picking>().lambda().eq(Picking::getCode, pickingRegister.getPickingCode()).eq(Picking::getDeleted, 0).eq(Picking::getGroupCode,groupCode));
        log.info("PDA批量拣货确认，获取拣货单信息，{}",JSONUtil.toJsonStr(picking));
        if (picking == null) {
            log.error("PDA批量拣货确认，拣货单{}不存在，请确认",pickingRegister.getPickingCode());
            throw new ServiceException("拣货单不存在，请确认");
        }
        if (picking.getStatus().equals(PickingStatusEnum.FINISH_PICK.getValue())) {
            log.error("PDA批量拣货确认，拣货单{}已完成，无法再拣货，请确认",pickingRegister.getPickingCode());
            throw new ServiceException("拣货单已完成，无法再拣货，请确认");
        }
        // 检查明细是否拣货完成了
        PickingItem pickingItem = iPickingItemService.getOne(new QueryWrapper<PickingItem>().lambda()
                .eq(PickingItem::getGroupCode,groupCode)
                .eq(PickingItem::getCode, pickingRegister.getPickingItemCode())
                .eq(PickingItem::getDeleted, 0));
        log.info("PDA批量拣货确认，获取拣货单明细，检查是否拣货完成，{}",JSONUtil.toJsonStr(pickingItem));
        BigDecimal differenceQuantity = pickingItem.getDistributionQuantity().subtract(pickingItem.getPickingQuantity()).subtract(pickingItem.getStockoutQuantity()).subtract(pickingRegister.getPickingQuantity());
        if (differenceQuantity.compareTo(BigDecimal.ZERO) < 0) {
            log.error("PDA批量拣货确认，拣货数量与缺货数量之和超过了计划数量，无法进行拣货确认");
            throw new ServiceException("拣货数量与缺货数量之和超过了计划数量，无法进行拣货确认");
        }
        // 占用容器
        if (StrUtil.isNotBlank(pickingRegister.getContainerBarCode())) {
            iContainerService.occupyContainerByPicking(picking.getCode(), pickingRegister.getContainerBarCode());
            //波次拣货，容器号赋值再拣货主单上
            this.update(new LambdaUpdateWrapper<Picking>()
                    .eq(Picking::getCode, pickingCode)
                    .eq(Picking::getGroupCode,groupCode)
                    .set(Picking::getContainerBarCode, pickingRegister.getContainerBarCode())
                    .set(Picking::getOccupy, NUM_ONE));
        }

        WmsTask task = iWmsTaskService.getOne(new QueryWrapper<WmsTask>().lambda().eq(WmsTask::getSourceCode, picking.getCode()).eq(WmsTask::getGroupCode,groupCode));

        // 添加判断是否有相同数据，然后保存
        pickingRegister.setTaskCode(task.getCode());
        iPickingRegisterService.distinctSave(pickingRegister);

        //更新拣货单信息(包含拣货主单，拣货明细，拣货确认单信息)
        updatePickingInfo(pickingRegister.getPickingItemCode(),pickingRegister.getPickingQuantity(),pickingRegister.getPickingCode(),isBatch);

        // 批量操作与单条操作时，更新发货单相关信息逻辑有异
        if (isBatch) {
            iDeliveryDistributionService.update(new UpdateWrapper<DeliveryDistribution>().lambda()
                    .eq(DeliveryDistribution::getGroupCode,groupCode)
                    .eq(DeliveryDistribution::getWavePickingCode, wavePickingCode)
                    .eq(DeliveryDistribution::getOldInventoryCode, oldInventoryCode)
                    .eq(DeliveryDistribution::getDeleted, NUM_ZERO)
                    .setSql(" picking_quantity = distribution_quantity")
                    .setSql("not_recheck_quantity = picking_quantity"));
            // 更新订单明细中的拣货数量
            iDeliveryItemService.updatePickingQuantityStatusByWavePickingCode(wavePickingCode,DeliveryStatusEnum.UN_RECHECK.getValue(),groupCode);
            //更新订单的状态和数量
            iDeliveryService.updateStatusAndQuantity(wavePickingCode);


        } else {
            //  更新发货单、明细、分配单的拣货数量
            updateDeliveryInfo(picking.getDeliveryCode(), null);
        }
        //更新拣货单状态
        List<Delivery> deliveryList = iDeliveryService.list(new QueryWrapper<Delivery>().lambda().eq(Delivery::getWavePickingCode, wavePickingCode).eq(Delivery::getGroupCode,groupCode));
        deliveryList.forEach(e-> iDeliveryService.updateDelvieryStatus(e));
        // 更新任务已完成作业量，作业状态在占用作业时就已更新，所以不需要再更新
        iWmsTaskService.update(new UpdateWrapper<WmsTask>().lambda().eq(WmsTask::getSourceCode, picking.getCode())
                .eq(WmsTask::getGroupCode,groupCode)
                .setSql(" job_quantity = job_quantity + " + pickingRegister.getPickingQuantity()));


        // 尝试根据数量判断并更新子单状态
        int finishItemFlag = iPickingItemService.updateStatusIfFinish(pickingRegister.getPickingItemCode(), PickingStatusEnum.FINISH_PICK.getValue());
        if (finishItemFlag > 0) {
            // 如果子单状态更新了，说明子单的分配拣货量 = 实际拣货量 + 缺货数量，则可将用于辅助的待拣货确认数据删除
            iPickingRegisterService.remove(new QueryWrapper<PickingRegister>().lambda().eq(PickingRegister::getPickingItemCode, pickingRegister.getPickingItemCode())
                    .eq(PickingRegister::getGroupCode,groupCode)
                    .eq(PickingRegister::getStatus, PickingRegisterStatusEnum.NOT_PICK.getValue()));

            // 明细完成，可跳转到上一个页面
            switchPageVO.setToPage(NUM_ONE);
            // 判断当前库位上还有未拣货的信息
            List<PickingRegister> unPickingRegisters = iPickingRegisterService.list(new QueryWrapper<PickingRegister>().lambda()
                    .eq(PickingRegister::getGroupCode,groupCode)
                    .eq(PickingRegister::getPickingCode, pickingRegister.getPickingCode())
                    .eq(PickingRegister::getLocation, pickingRegister.getLocation()).eq(PickingRegister::getStatus, PickingRegisterStatusEnum.NOT_PICK.getValue()));
            if (IterUtil.isEmpty(unPickingRegisters)) {
                switchPageVO.setToPage(NUM_TWO);
            }

            // 再尝试触发主单拣货完成逻辑
            int finishPickingFlag = this.updateStatusIfFinish(pickingRegister.getPickingCode(), PickingStatusEnum.FINISH_PICK.getValue());
            if (finishPickingFlag > 0) {
                //先将订单状态都改为“待出库”以匹配后面的业务逻辑
                iDeliveryService.update(new UpdateWrapper<Delivery>().lambda().eq(Delivery::getGroupCode,groupCode).eq(Delivery::getWavePickingCode, wavePickingCode).set(Delivery::getStatus, DeliveryStatusEnum.UN_DELIVERY));
                // 拣货单完成后的操作
                afterFinishPicking(picking.getCode());

                // 单据完成，可跳转到拣货列表页面
                switchPageVO.setToPage(NUM_THREE);
            } else {
                // 更新主单状态为拣货中
                this.update(new UpdateWrapper<Picking>().lambda().eq(Picking::getCode, pickingCode) .eq(Picking::getGroupCode,groupCode).set(Picking::getStatus, PickingStatusEnum.PICKING.getValue()));
            }
        } else {
            // 更新子单、主单状态为拣货中
            iPickingItemService.update(new UpdateWrapper<PickingItem>().lambda().eq(PickingItem::getCode, pickingItemCode) .eq(PickingItem::getGroupCode,groupCode).set(PickingItem::getStatus, PickingStatusEnum.PICKING.getValue()));
            this.update(new UpdateWrapper<Picking>().lambda().eq(Picking::getCode, pickingCode).eq(Picking::getGroupCode,groupCode).set(Picking::getStatus, PickingStatusEnum.PICKING.getValue()));
        }

        return switchPageVO;
    }

    @Override
    public void validatePnInfo(String pickingCode,String goodsBarCode,String pn,String groupCode) {
        String shipmentCode = iPickingItemService.getShipmentCodeByPickingCode(pickingCode,groupCode);
        String goodsCode = goodsPnService.getGoodsCode(shipmentCode, pn);
        Goods goods = goodsCache.getObj(goodsCode);
        if (goods == null || !goods.getBarCode().equals(goodsBarCode)) {
            throw new ServiceException("输入的pn码不正确，请确认");
        }
    }

    /**
     * 删除拣货单
     * @param wavePickingCode
     */
    @Override
    @Transactional(rollbackFor = {Exception.class})
    public void cancelWaveTips(String wavePickingCode) {
        try {
            baseMapper.cancelWaveTips(wavePickingCode);
        }catch (Exception e){
            e.printStackTrace();
            log.info("删除拣货单失败" + e.getMessage());
        }
    }

    /**
     * 更新拣货单信息(包含拣货主单，拣货明细，拣货确认单信息)
     */
    @Transactional(rollbackFor = {Exception.class})
    public void updatePickingInfo(String pickingItemCode,BigDecimal pickingQuantity,String pickingCode,boolean isBatch) {
        // 更新拣货确认表中那条未拣货数据的拣货数量
        //如果时生成波次的拣货单，拣货数量初始值为0，所已不对其进行做差操作，防止出现负数
        if (isBatch) {
            iPickingRegisterService.update(new UpdateWrapper<PickingRegister>().lambda().eq(PickingRegister::getPickingItemCode, pickingItemCode)
                    .eq(PickingRegister::getGroupCode,UserUtil.getBranchCode())
                    .eq(PickingRegister::getDeleted, 0).eq(PickingRegister::getStatus, PickingRegisterStatusEnum.NOT_PICK.getValue())
                    .setSql(" not_picking_quantity = not_picking_quantity - " + pickingQuantity));
        } else {
            iPickingRegisterService.update(new UpdateWrapper<PickingRegister>().lambda().eq(PickingRegister::getPickingItemCode, pickingItemCode)
                    .eq(PickingRegister::getGroupCode,UserUtil.getBranchCode())
                    .eq(PickingRegister::getDeleted, 0).eq(PickingRegister::getStatus, PickingRegisterStatusEnum.NOT_PICK.getValue())
                    .setSql(" not_picking_quantity = not_picking_quantity - " + pickingQuantity)
                    .setSql(" picking_quantity = picking_quantity - " + pickingQuantity));
        }

        // 更新明细拣货数量
        iPickingItemService.update(new UpdateWrapper<PickingItem>().lambda().eq(PickingItem::getCode, pickingItemCode)
                .eq(PickingItem::getGroupCode,UserUtil.getBranchCode())
                .setSql(" picking_quantity = picking_quantity + " + pickingQuantity));
        // 更新主单拣货数量、待拣货数量
        this.update(new UpdateWrapper<Picking>().lambda().eq(Picking::getCode, pickingCode)
                .eq(Picking::getGroupCode,UserUtil.getBranchCode())
                .setSql(" picking_quantity = picking_quantity + " + pickingQuantity)
                .setSql(" not_picking_quantity = not_picking_quantity - " + pickingQuantity));
    }
    /**
     * @param pickingCode:
     * @param newStatus:
     * @description 尝试更新主单状态，试探明细是否完成拣货
     * @author xuyang
     * @date 2020/10/26 10:41
     **/
    @Override
    @Transactional(rollbackFor = {Exception.class})
    public int updateStatusIfFinish(String pickingCode, String newStatus) {
        return this.baseMapper.updateStatusIfFinish(pickingCode, newStatus,UserUtil.getBranchCode());
    }

    /**
     * @param pickingItemCode
     * @param stockoutQuantity
     * @description PDA缺货登记
     * @author xuyang
     * @date 2020/10/26 13:39
     **/
    @Override
    @Transactional(rollbackFor = {Exception.class})
    public PdaSwitchPageVO stockout(String pickingCode, String pickingItemCode, BigDecimal stockoutQuantity) {
        PdaSwitchPageVO switchPageVO = new PdaSwitchPageVO();
        // 默认PDA不跳转
        switchPageVO.setToPage(NumConst.NUM_ZERO);
        switchPageVO.setMsg("操作成功");

        if (stockoutQuantity == null || BigDecimal.ZERO.compareTo(stockoutQuantity) >= 0) {
            throw new ServiceException("缺货数量必须大于0");
        }
        // 更新任务状态
        iWmsTaskService.updateTaskWorkingBySourceCode(pickingCode);

        // 检查主单状态
        Picking picking = getOne(new QueryWrapper<Picking>().lambda().eq(Picking::getCode, pickingCode).eq(Picking::getGroupCode,UserUtil.getBranchCode()).eq(Picking::getDeleted, 0).eq(Picking::getGroupCode,UserUtil.getBranchCode()));
        if (picking == null) {
            throw new ServiceException("拣货单不存在，请确认");
        }
        if (picking.getStatus().equals(PickingStatusEnum.FINISH_PICK.getValue())) {
            throw new ServiceException("拣货单已完成，无法再拣货，请确认");
        }

        // 尝试更新子单缺货量，如果执行成功，说明该数没有超过允许数量
        int updateStockoutQuantityFlag = iPickingItemService.updateStockout(pickingItemCode, stockoutQuantity);
        if (updateStockoutQuantityFlag == 0) {
            throw new ServiceException("缺货数量超过了待拣货数量，请确认");
        }
        // 更新未拣货确认单中的待拣货数量
        iPickingRegisterService.update(new UpdateWrapper<PickingRegister>().lambda()
                .eq(PickingRegister::getGroupCode,UserUtil.getBranchCode())
                .eq(PickingRegister::getPickingItemCode, pickingItemCode).eq(PickingRegister::getDeleted, 0)
                .eq(PickingRegister::getStatus, PickingRegisterStatusEnum.NOT_PICK.getValue())
                .setSql(" not_picking_quantity = not_picking_quantity -" + stockoutQuantity)
                .setSql(" picking_quantity = picking_quantity -" + stockoutQuantity));
        // 更新主单缺货数量、未拣货数量
        this.update(new UpdateWrapper<Picking>().lambda().eq(Picking::getCode, pickingCode)
                .eq(Picking::getGroupCode,UserUtil.getBranchCode())
                .setSql(" not_picking_quantity = not_picking_quantity -" + stockoutQuantity)
                .setSql(" stockout_quantity = stockout_quantity + " + stockoutQuantity));

        // 缺货登记返还库存
        PickingItem pickingItem = iPickingItemService.getOne(new QueryWrapper<PickingItem>().lambda().eq(PickingItem::getCode, pickingItemCode) .eq(PickingItem::getGroupCode,UserUtil.getBranchCode()));
        iInventoryService.stockoutInventory(pickingItem.getInventoryCode(), stockoutQuantity, StockoutTypeEnum.PICKING.getValue(), pickingCode);

        // 更新发货单信息
        updateDeliveryInfo(picking.getDeliveryCode(),null);

        // 判断子单、主单是否完成
        int finishItemFlag = iPickingItemService.updateStatusIfFinish(pickingItemCode, PickingStatusEnum.FINISH_PICK.getValue());
        if (finishItemFlag > 0) {
            // 明细复核完成，则可跳转到复核详情页面
            // 判断当前库位上还有未拣货的信息
            switchPageVO.setToPage(NUM_ONE);
            List<PickingRegister> unPickingRegisters = iPickingRegisterService.list(new QueryWrapper<PickingRegister>().lambda()
                    .eq(PickingRegister::getGroupCode,UserUtil.getBranchCode())
                    .eq(PickingRegister::getPickingCode, pickingItem.getPickingCode())
                    .eq(PickingRegister::getLocation, pickingItem.getLocation()).eq(PickingRegister::getStatus, PickingRegisterStatusEnum.NOT_PICK.getValue()));
            if (IterUtil.isEmpty(unPickingRegisters)) {
                switchPageVO.setToPage(NUM_TWO);
            }

            // 如果子单状态更新了，说明子单的分配拣货量 = 实际拣货量 + 缺货数量，则可将用于辅助的待拣货确认数据删除
            iPickingRegisterService.remove(new QueryWrapper<PickingRegister>().lambda().eq(PickingRegister::getPickingItemCode, pickingItemCode) .eq(PickingRegister::getGroupCode,UserUtil.getBranchCode()).eq(PickingRegister::getStatus, PickingRegisterStatusEnum.NOT_PICK.getValue()));

            // 再尝试触发主单拣货完成逻辑
            int finishPickingFlag = this.updateStatusIfFinish(pickingCode, PickingStatusEnum.FINISH_PICK.getValue());
            if (finishPickingFlag > 0) {
                // 拣货单完成后的操作
                afterFinishPicking(pickingCode);

                // 主单也完成，则可跳转到复核列表页面
                switchPageVO.setToPage(NUM_THREE);
            } else {
                // 如果整个单子没完成，则将任务设置为作业中
                iWmsTaskService.update(new UpdateWrapper<WmsTask>().lambda().eq(WmsTask::getSourceCode, pickingCode)
                        .eq(WmsTask::getGroupCode,UserUtil.getBranchCode())
                        .set(WmsTask::getJobStatus, WmsTaskJobStatusEnum.WORKING)
                        .set(WmsTask::getOperatorBy, UserUtil.getCode())
                        .set(WmsTask::getOperatorName, partyCache.translate(UserUtil.getCode())));
                // 更新主单为拣货中状态
                update(new UpdateWrapper<Picking>().lambda().eq(Picking::getCode, pickingCode).eq(Picking::getGroupCode,UserUtil.getBranchCode()).set(Picking::getStatus, PickingStatusEnum.PICKING.getValue()));
            }
        } else {
            // 更新明细、主单状态
            iPickingItemService.update(new UpdateWrapper<PickingItem>().lambda().eq(PickingItem::getCode, pickingItemCode)
                    .eq(PickingItem::getGroupCode,UserUtil.getBranchCode())
                    .set(PickingItem::getStatus, PickingStatusEnum.PICKING.getValue()));
            update(new UpdateWrapper<Picking>().lambda().eq(Picking::getCode, pickingCode).eq(Picking::getGroupCode,UserUtil.getBranchCode()).set(Picking::getStatus, PickingStatusEnum.PICKING.getValue()));
        }

        return switchPageVO;
    }

    /**
     * @param pickingCode:
     * @description 拣货单完成后的操作
     * @author xuyang
     * @date 2020/10/26 11:14
     **/
    @Override
    @Transactional(rollbackFor = {Exception.class})
    public Result afterFinishPicking(String pickingCode) {
        // 生成出库单
        Result result = generateOutboundInfo(pickingCode);
        if (!result.isOk()) {
            return result;
        }
        //List<PickingItem> items = iPickingItemService.list(new QueryWrapper<PickingItem>().lambda().eq(PickingItem::getPickingCode, pickingCode)
        //        .eq(PickingItem::getDeleted, 0));
        //
        //updateLocationActionNum(items);

        // 更新相关的任务，将任务设为完成
        //WmsTask task = iWmsTaskService.getOne(new QueryWrapper<WmsTask>().lambda().eq(WmsTask::getSourceCode, pickingCode));
        //if (task != null) {
        //    iWmsTaskService.update(new UpdateWrapper<WmsTask>().lambda().eq(WmsTask::getSourceCode, pickingCode)
        //            .set(WmsTask::getJobStatus, WmsTaskJobStatusEnum.COMPLETED)
        //            .set(WmsTask::getOperatorBy, UserUtil.getCode())
        //            .set(WmsTask::getOperatorName, partyCache.translate(UserUtil.getCode())));
        //    // 更新任务作业量
        //    iWmsTaskService.pickingUpdateJobQuantity(pickingCode);
        //}
        // 更新相关的任务，将任务设为完成
        iWmsTaskService.endTask(pickingCode,UserUtil.getCode(),partyCache.translate(UserUtil.getCode()));


        // 临时库位上的库存可以将作业状态改为正常
        //items.forEach(e -> {
        //    iInventoryService.releaseTempInvJobStatus(e.getInventoryCode());
        //});

        //更新动碰次数
        this.baseMapper.updateActionNum(pickingCode);
        // 临时库位上的库存可以将作业状态改为正常
        this.baseMapper.releaseTempInvJobStatus(pickingCode);




        return Result.ok();
    }

    /**
     * 更新发货模块各表的拣货数量
     *
     * @param deliveryCode
     * @return
     */
    @Transactional(rollbackFor = {Exception.class})
    public Result updateDeliveryInfo(String deliveryCode,String wavePickingCode) {
        String groupCode = UserUtil.getBranchCode();
        // 更新分配单拣货数量
        iDeliveryDistributionService.updatePickingQuantityForCheck(deliveryCode,groupCode);
        // 更新订单明细中的拣货数量
        iDeliveryItemService.updatePickingQuantityForCheck(deliveryCode,groupCode);
        // 更新订单中的总拣货数量
        iDeliveryService.updatePickNumByDistributionData(deliveryCode);

        // 更新发货单状态
        if (StrUtil.isEmpty(wavePickingCode)) {
            iDeliveryService.updateDeliveryStatusIfEmptyDistribution(deliveryCode,groupCode);
        } else {
            //波次号wavePickingCode不为空时，更新发货单的状态为待复核
            iDeliveryService.updateStatusByWavePickingCode(wavePickingCode,DeliveryStatusEnum.UN_RECHECK.getValue(),groupCode);
            iDeliveryItemService.update(new UpdateWrapper<DeliveryItem>().lambda().eq(DeliveryItem::getWavePickingCode, wavePickingCode)
                    .eq(DeliveryItem::getGroupCode,groupCode)
                .set(DeliveryItem::getStatus, DeliveryStatusEnum.UN_RECHECK.getValue()));
        }
        return Result.ok();
    }

    /**
     * PC端批量确认拣货单
     *
     * @param pickingCode
     * @param isStockout
     * @return
     */
    @Transactional(rollbackFor = {Exception.class})
    @Override
    public void PcBatchCheck(String pickingCode,boolean isStockout) {

        log.info("拣货确认。拣货单号：{}",pickingCode);
        // 判断拣货单及状态
        Picking picking = this.getOne(new LambdaQueryWrapper<Picking>().select(Picking::getStatus,Picking::getWavePickingCode).eq(Picking::getGroupCode,UserUtil.getBranchCode()).eq(Picking::getCode, pickingCode));
        log.info("拣货确认。拣货单：{}",JSONUtil.toJsonStr(picking));
        if (picking == null) {
            log.error("拣货单拣货确认失败。拣货单不存在。拣货单:{}",JSONUtil.toJsonStr(picking));
            throw new ServiceException("拣货单不存在，请确认。");
        }
        // 不是缺货登记触发拣货完成，“待拣货”、“拣货中”状态可以拣货确认，其他状态不允许确认
        if (!isStockout) {
            if ( !StrUtil.equalsAny(picking.getStatus(), PickingStatusEnum.NOT_PICK.getValue(), PickingStatusEnum.PICKING.getValue())) {
                log.error("拣货单已完成，无法再拣货，请确认。拣货单:{}",JSONUtil.toJsonStr(picking));
                throw new ServiceException("拣货单已完成，无法再拣货，请确认。");
            }
        }

        String wavePickingCode = picking.getWavePickingCode();

        //更新未拣货的商品拣货登记数量
        iPickingRegisterService.update(new UpdateWrapper<PickingRegister>().lambda()
                .eq(PickingRegister::getGroupCode,UserUtil.getBranchCode())
                .eq(PickingRegister::getStatus, PickingStatusEnum.NOT_PICK.getValue())
                .eq(PickingRegister::getWavePickingCode,wavePickingCode)
                .setSql("picking_quantity=distribution_quantity")
                .set(PickingRegister::getStatus,PickingStatusEnum.FINISH_PICK.getValue()));
        //更新拣货明细数量
        iPickingItemService.update(new UpdateWrapper<PickingItem>().lambda()
                .eq(PickingItem::getGroupCode,UserUtil.getBranchCode())
                .eq(PickingItem::getStatus, PickingStatusEnum.NOT_PICK.getValue())
                .eq(PickingItem::getWavePickingCode, wavePickingCode)
                .setSql("picking_quantity=distribution_quantity")
                .set(PickingItem::getStatus,PickingStatusEnum.FINISH_PICK.getValue()));
        //更新拣货单拣货确认完成  (由于缺货登记的时候已经更新了拣货单状态，所以此处会导致更新失败)
       int flag = this.baseMapper.updateCheckFinsh(pickingCode,UserUtil.getBranchCode());
     /*  if(flag<1){
           log.error("拣货单拣货确认失败。更新拣货单拣货数量异常，请检查拣货单数据。");
           throw new ServiceException("拣货单拣货确认失败！");
       }*/


        //  更新订单分配表拣货数量
        iDeliveryDistributionService.update(new UpdateWrapper<DeliveryDistribution>().lambda()
                .eq(DeliveryDistribution::getGroupCode,UserUtil.getBranchCode())
                .eq(DeliveryDistribution::getWavePickingCode, wavePickingCode)
                .setSql("picking_quantity=plan_quantity-pick_stock_out_num")
                .setSql("not_recheck_quantity = picking_quantity"));
        //更新订单明细表拣货数量和状态
        iDeliveryItemService.updatePickingQuantityStatusByWavePickingCode(wavePickingCode, DeliveryStatusEnum.UN_RECHECK.getValue(),UserUtil.getBranchCode());
        //iDeliveryItemService.update(new UpdateWrapper<DeliveryItem>().lambda()
        //        .eq(DeliveryItem::getWavePickingCode, wavePickingCode)
        //    .set(DeliveryItem::getStatus, DeliveryStatusEnum.UN_RECHECK.getValue()));
        // 更新订单拣货数量
        flag = iDeliveryService.updatePickingQuantityByWavePickingCode(wavePickingCode,UserUtil.getBranchCode());
        /*if(flag<1){
            log.error("拣货单拣货确认失败。更新发货单拣货数量异常，请检查拣货单数据。");
            throw new ServiceException("拣货单拣货确认失败！");
        }*/

        //拣货完成操作生成出库单
         afterFinishPicking(pickingCode);
    }

    /**
     * 批量保存拣货单、拣货明细
     *
     * @param pickings
     */
    @Override
    @Transactional(rollbackFor = {Exception.class})
    public void batchSavePickingAndItemAndRegisters(List<Picking> pickings) {
        LocalDateTime nowTime = LocalDateTime.now();
        String createdBy = UserUtil.getCode();
        String createdName = partyCache.translate(createdBy);

        // 获取拣货明细集合
        List<PickingItem> pickingItems = new ArrayList<>();
        pickings.stream().forEach(e->pickingItems.addAll(e.getPickingItemList()));

        // 批量保存主单信息
        int insertNum = batchSavePickings(pickings, createdBy, createdName, nowTime);
        if (insertNum > 0) {
            // 批量保存子单信息
            insertNum = batchSavePickingItems(pickingItems, createdBy, createdName, nowTime);
            if (insertNum <= 0) {
                throw new ServiceException("批量保存拣货单信息失败");
            } else {
                insertNum = batchSavePickingRegisters(pickingItems, createdBy, createdName, nowTime);
                if (insertNum <= 0) {
                    throw new ServiceException("批量保存拣货单信息失败");
                }
            }
        } else {
            throw new ServiceException("批量保存拣货单信息失败");
        }
    }

    /**
     * 批量保存拣货单
     * @param pickings
     * @return
     */
    private int batchSavePickings(List<Picking> pickings, String createdBy, String createdName, LocalDateTime nowTime) {
        StringBuilder data = new StringBuilder();
        data.append("INSERT INTO `wms_picking`( `id`, `code`, `warehouse_code`, `shipment_code`, `shipment_name`, " +
                "`customer_code`, `delivery_code`, `group_code`, `source_code`, `type`, `plan_quantity`,`order_tag`, " +
                "`picking_quantity`, `stockout_quantity`, `not_picking_quantity`, `status`, `remarks`, `is_issue`, `is_review`, " +
                "`is_oms_order`, `wave_picking_code`, `goods_sort_quantity`,`prepackaged_code`,`is_prepackaged`, `is_deleted`," +
                "`logistics_code`,`temporary_code`,`carrier_code`,`carrier_name`, " +
                "`created_by`, `created_name`, `gmt_created`, `modified_by`, `modified_name`, `gmt_modified`) values ");
        String sql = pickings.stream().map(e -> makePickingInsertValueStr(e, createdBy, createdName, nowTime)).collect(Collectors.joining(","));
        data.append(sql);
        return this.baseMapper.execSql(data.toString());
    }

    /**
     * 生成拣货单主单sql的data数据
     * @param picking
     * @return
     */
    private StringBuilder makePickingInsertValueStr(final Picking picking, String createdBy, String createdName, LocalDateTime nowTime) {
        StringBuilder data = new StringBuilder();
        data.append("(");
        // id
        data.append("default, ");
        // code
        data.append("'").append(picking.getCode()).append("', ");
        // warehouse_code
        data.append("'").append(picking.getWarehouseCode()).append("', ");
        // shipment_code
        data.append("'").append(picking.getShipmentCode()).append("', ");
        // shipment_name
        data.append("'").append(picking.getShipmentName()).append("', ");
        // customer_code
        data.append("'").append(ObjectUtil.defaultIfNull(picking.getCustomerCode(), StrUtil.EMPTY)).append("', ");
        // delivery_code
        data.append("'").append(picking.getDeliveryCode()).append("', ");
        // group_code
        data.append("'").append(picking.getGroupCode()).append("', ");
        // source_code
        data.append("'").append(ObjectUtil.defaultIfNull(picking.getSourceCode(), StrUtil.EMPTY)).append("', ");
        // type
        data.append("'").append(picking.getType()).append("', ");
        // plan_quantity
        data.append("'").append(ObjectUtil.defaultIfNull(picking.getPlanQuantity(), BigDecimal.ZERO)).append("', ");
        // order_tag
        data.append("'").append(picking.getOrderTag()).append("', ");
        // picking_quantity
        data.append("'").append(BigDecimal.ZERO).append("', ");
        // stockout_quantity
        data.append("'").append(BigDecimal.ZERO).append("', ");
        // not_picking_quantity
        data.append("'").append(picking.getNotPickingQuantity()).append("', ");
        // status
        data.append("'").append(PickingStatusEnum.NOT_PICK.getValue()).append("', ");
        // remarks
        data.append("'").append(picking.getRemarks()).append("', ");
        // is_issue
        data.append("'").append(NUM_ZERO).append("', ");
        // is_review
        data.append("'").append(picking.getReview()).append("', ");
        // is_oms_order
        data.append("'").append(picking.getIsOmsOrder()).append("', ");
        // wave_picking_code
        data.append("'").append(picking.getWavePickingCode()).append("', ");
        // goods_sort_quantity
        data.append("'").append(ObjectUtil.defaultIfNull(picking.getGoodsSortQuantity(), BigDecimal.ZERO)).append("', ");
        // prepackaged_code
        data.append("'").append(ObjectUtil.defaultIfNull(picking.getPrepackagedCode(), StrUtil.EMPTY)).append("', ");
        // is_prepackaged
        data.append("'").append(picking.getPrepackaged()).append("', ");
        // is_deleted
        data.append("'").append(NUM_ZERO).append("', ");
        // logistics_code
        data.append("'").append(ObjectUtil.defaultIfNull(picking.getLogisticsCode(), StrUtil.EMPTY)).append("', ");
        // temporary_code
        data.append("'").append(ObjectUtil.defaultIfNull(picking.getTemporaryCode(), StrUtil.EMPTY)).append("', ");
        // carrier_code
        data.append("'").append(ObjectUtil.defaultIfNull(picking.getCarrierCode(), StrUtil.EMPTY)).append("', ");
        // carrier_name
        data.append("'").append(ObjectUtil.defaultIfNull(picking.getCarrierName(), StrUtil.EMPTY)).append("', ");
        // created_by
        data.append("'").append(createdBy).append("', ");
        // created_name
        data.append("'").append(createdName).append("', ");
        // gmt_created
        data.append("'").append(nowTime).append("', ");
        // modified_by
        data.append("'").append(createdBy).append("', ");
        // modified_name
        data.append("'").append(createdName).append("', ");
        // gmt_modified
        data.append("'").append(nowTime).append("'");
        data.append(")");
        return data;
    }


    /**
     * 批量保存拣货明细单
     * @param items
     * @return
     */
    private int batchSavePickingItems(List<PickingItem> items, String createdBy, String createdName, LocalDateTime nowTime) {
        StringBuilder data = new StringBuilder();
        data.append("INSERT INTO `wms_picking_item`(  `id`, `code`, `picking_code`, `delivery_distribution_code`, " +
                "`source_item_code`, `delivery_item_code`,`group_code`, `goods_code`, `good_code`, `goods_name`, `goods_bar_code`, " +
                "`spec`, `expiration_quantity`, `goods_unit`, `goods_unit_name`, `basic_unit`, `basic_unit_name`, " +
                "`lot`, `receive_lot`, `gmt_manufacture`, `gmt_expire`, `gmt_stock`, `supplier`, `serial_number`, " +
                "`extend_one`, `extend_two`, `extend_three`, `extend_four`, `extend_five`, `extend_six`, `goods_status`," +
                " `goods_status_name`, `warehouse_code`, `warehouse_space_code`, `location`, `old_location`, " +
                "`distribution_quantity`, `picking_quantity`, `stockout_quantity`, `remarks`, `basic_quantity`, " +
                "`inventory_code`, `old_inventory_code`, `is_lock`, `status`, `inventory_container_bar_code`, " +
                "`sale_item_code`, `is_consignment`, `plan_quantity`, `wave_picking_code`, " +
                "`created_by`, `created_name`, `gmt_created`, `modified_by`, `modified_name`, `gmt_modified`) values ");
        String sql = items.stream().map(e -> makePickingItemsInsertValueStr(e, createdBy, createdName, nowTime)).collect(Collectors.joining(","));
        data.append(sql);
        return this.baseMapper.execSql(data.toString());
    }

    /**
     * 生成拣货明细sql的data数据
     * @param item
     * @return
     */
    private StringBuilder makePickingItemsInsertValueStr(final PickingItem item, String createdBy, String createdName, LocalDateTime nowTime) {
        StringBuilder data = new StringBuilder();
        data.append("(");
        // id
        data.append("default, ");
        // code
        data.append("'").append(item.getCode()).append("', ");
        // picking_code
        data.append("'").append(item.getPickingCode()).append("', ");
        // delivery_distribution_code
        data.append("'").append(item.getDeliveryDistributionCode()).append("', ");
        // source_item_code
        data.append("'").append(StrUtil.blankToDefault(item.getSourceItemCode(), StrUtil.EMPTY)).append("', ");
        // delivery_item_code
        data.append("'").append(item.getDeliveryItemCode()).append("', ");
        // group_code
        data.append("'").append(item.getGroupCode()).append("', ");
        // goods_code
        data.append("'").append(item.getGoodsCode()).append("', ");
        // good_code
        data.append("'").append(item.getGoodCode()).append("', ");
        // goods_name
        data.append("'").append(item.getGoodsName()).append("', ");
        // goods_bar_code
        data.append("'").append(item.getGoodsBarCode()).append("', ");
        // spec
        data.append("'").append(StrUtil.blankToDefault(item.getSpec(), StrUtil.EMPTY)).append("', ");
        // expiration_quantity
        data.append("'").append(ObjectUtil.defaultIfNull(item.getExpirationQuantity(), BigDecimal.ZERO)).append("', ");
        // goods_unit
        data.append("'").append(item.getGoodsUnit()).append("', ");
        // goods_unit_name
        data.append("'").append(item.getGoodsUnitName()).append("', ");
        // basic_unit
        data.append("'").append(item.getBasicUnit()).append("', ");
        // basic_unit_name
        data.append("'").append(item.getBasicUnitName()).append("', ");
        // lot
        data.append("'").append(ObjectUtil.defaultIfNull(item.getLot(), StrUtil.EMPTY)).append("', ");
        // receive_lot
        data.append("'").append(ObjectUtil.defaultIfNull(item.getReceiveLot(), StrUtil.EMPTY)).append("', ");
        // gmt_manufacture
        if (ObjectUtil.isNull(item.getGmtManufacture())) {
            data.append(StrUtil.NULL).append(", ");
        } else {
            data.append("'").append(item.getGmtManufacture()).append("', ");
        }
        // gmt_expire
        if (ObjectUtil.isNull(item.getGmtExpire())) {
            data.append(StrUtil.NULL).append(", ");
        } else {
            data.append("'").append(item.getGmtExpire()).append("', ");
        }
        // gmt_stock
        if (ObjectUtil.isNull(item.getGmtStock())) {
            data.append(StrUtil.NULL).append(", ");
        } else {
            data.append("'").append(item.getGmtStock()).append("', ");
        }
        // supplier
        data.append("'").append(ObjectUtil.defaultIfNull(item.getSupplier(), StrUtil.EMPTY)).append("', ");
        // serial_number
        data.append("'").append(ObjectUtil.defaultIfNull(item.getSerialNumber(), StrUtil.EMPTY)).append("', ");
        // extend_one
        data.append("'").append(ObjectUtil.defaultIfNull(item.getExtendOne(), StrUtil.EMPTY)).append("', ");
        // extend_two
        data.append("'").append(ObjectUtil.defaultIfNull(item.getExtendTwo(), StrUtil.EMPTY)).append("', ");
        // extend_three
        if (ObjectUtil.isNull(item.getExtendThree())) {
            data.append(StrUtil.NULL).append(", ");
        } else {
            data.append("'").append(item.getExtendThree()).append("', ");
        }
        // extend_four
        if (ObjectUtil.isNull(item.getExtendFour())) {
            data.append(StrUtil.NULL).append(", ");
        } else {
            data.append("'").append(item.getExtendFour()).append("', ");
        }
        // extend_five
        if (ObjectUtil.isNull(item.getExtendFive())) {
            data.append(StrUtil.NULL).append(", ");
        } else {
            data.append("'").append(item.getExtendFive()).append("', ");
        }
        // extend_six
        if (ObjectUtil.isNull(item.getExtendSix())) {
            data.append(StrUtil.NULL).append(", ");
        } else {
            data.append("'").append(item.getExtendSix()).append("', ");
        }
        // goods_status
        data.append("'").append(item.getGoodsStatus()).append("', ");
        // goods_status_name
        data.append("'").append(item.getGoodsStatusName()).append("', ");
        // warehouse_code
        data.append("'").append(item.getWarehouseCode()).append("', ");
        // warehouse_space_code
        data.append("'").append(item.getWarehouseSpaceCode()).append("', ");
        // location
        data.append("'").append(item.getLocation()).append("', ");
        // old_location
        data.append("'").append(ObjectUtil.defaultIfNull(item.getOldLocation(), StrUtil.EMPTY)).append("', ");
        // distribution_quantity
        data.append("'").append(item.getDistributionQuantity()).append("', ");
        // picking_quantity
        data.append("'").append(ObjectUtil.defaultIfNull(item.getPickingQuantity(), BigDecimal.ZERO)).append("', ");
        // stockout_quantity
        data.append("'").append(BigDecimal.ZERO).append("', ");
        // remarks
        data.append("'").append(ObjectUtil.defaultIfNull(item.getRemarks(), StrUtil.EMPTY)).append("', ");
        // basic_quantity
        data.append("'").append(ObjectUtil.defaultIfNull(item.getBasicQuantity(), BigDecimal.ZERO)).append("', ");
        // inventory_code
        data.append("'").append(item.getInventoryCode()).append("', ");
        // old_inventory_code
        data.append("'").append(ObjectUtil.defaultIfNull(item.getOldInventoryCode(), StrUtil.EMPTY)).append("', ");
        // is_lock
        data.append("'").append(item.getIsLock()).append("', ");
        // status
        data.append("'").append(item.getStatus()).append("', ");
        // inventory_container_bar_code
        data.append("'").append(ObjectUtil.defaultIfNull(item.getInventoryContainerBarCode(), StrUtil.EMPTY)).append("', ");
        // sale_item_code
        data.append("'").append(ObjectUtil.defaultIfNull(item.getSaleItemCode(), StrUtil.EMPTY)).append("', ");
        // is_consignment
        data.append("'").append(item.getIsConsignment()).append("', ");
        // plan_quantity
        data.append("'").append(item.getPlanQuantity()).append("', ");
        // wave_picking_code
        data.append("'").append(item.getWavePickingCode()).append("', ");
        // created_by
        data.append("'").append(createdBy).append("', ");
        // created_name
        data.append("'").append(createdName).append("', ");
        // gmt_created
        data.append("'").append(nowTime).append("', ");
        // modified_by
        data.append("'").append(createdBy).append("', ");
        // modified_name
        data.append("'").append(createdName).append("', ");
        // gmt_modified
        data.append("'").append(nowTime).append("' ");
        data.append(")");
        return data;
    }


    /**
     * 批量保存拣货确认单
     * @param items
     * @return
     */
    private int batchSavePickingRegisters(List<PickingItem> items, String createdBy, String createdName, LocalDateTime nowTime) {
        StringBuilder data = new StringBuilder();
        data.append("INSERT INTO `wms_picking_register`(  `id`, `code`, `picking_code`, `picking_item_code`, `delivery_distribution_code`, " +
                "`source_item_code`, `delivery_item_code`,`group_code`, `goods_code`, `good_code`, `goods_name`, `goods_bar_code`, " +
                "`spec`, `expiration_quantity`, `goods_unit`, `goods_unit_name`, `basic_unit`, `basic_unit_name`, " +
                "`lot`, `receive_lot`, `gmt_manufacture`, `gmt_expire`, `gmt_stock`, `supplier`, `serial_number`, " +
                "`extend_one`, `extend_two`, `extend_three`, `extend_four`, `extend_five`, `extend_six`, `goods_status`," +
                " `goods_status_name`, `warehouse_code`, `warehouse_space_code`, `location`, `old_location`, " +
                "`distribution_quantity`, `picking_quantity`,`not_picking_quantity`, `remarks`, `basic_quantity`, " +
                "`inventory_code`, `old_inventory_code`, `is_lock`, `status`, `inventory_container_bar_code`, " +
                "`sale_item_code`, `is_consignment`, `plan_quantity`, `wave_picking_code`, " +
                "`created_by`, `created_name`, `gmt_created`, `modified_by`, `modified_name`, `gmt_modified`) values ");
        String sql = items.stream().map(e -> makePickingRegistersInsertValueStr(e, createdBy, createdName, nowTime)).collect(Collectors.joining(","));
        data.append(sql);
        return this.baseMapper.execSql(data.toString());
    }

    /**
     * 生成拣货确认单sql的data数据
     * @param item
     * @return
     */
    private StringBuilder makePickingRegistersInsertValueStr(final PickingItem item, String createdBy, String createdName, LocalDateTime nowTime) {
        StringBuilder data = new StringBuilder();
        data.append("(");
        // id
        data.append("default, ");
        // code
        data.append("'").append(IdUtil.simpleUUID()).append("', ");
        // picking_code
        data.append("'").append(item.getPickingCode()).append("', ");
        // picking_item_code
        data.append("'").append(item.getCode()).append("', ");
        // delivery_distribution_code
        data.append("'").append(item.getDeliveryDistributionCode()).append("', ");
        // source_item_code
        data.append("'").append(StrUtil.blankToDefault(item.getSourceItemCode(), StrUtil.EMPTY)).append("', ");
        // delivery_item_code
        data.append("'").append(item.getDeliveryItemCode()).append("', ");
        // group_code
        data.append("'").append(item.getGroupCode()).append("', ");
        // goods_code
        data.append("'").append(item.getGoodsCode()).append("', ");
        // good_code
        data.append("'").append(item.getGoodCode()).append("', ");
        // goods_name
        data.append("'").append(item.getGoodsName()).append("', ");
        // goods_bar_code
        data.append("'").append(item.getGoodsBarCode()).append("', ");
        // spec
        data.append("'").append(StrUtil.blankToDefault(item.getSpec(), StrUtil.EMPTY)).append("', ");
        // expiration_quantity
        data.append("'").append(ObjectUtil.defaultIfNull(item.getExpirationQuantity(), BigDecimal.ZERO)).append("', ");
        // goods_unit
        data.append("'").append(item.getGoodsUnit()).append("', ");
        // goods_unit_name
        data.append("'").append(item.getGoodsUnitName()).append("', ");
        // basic_unit
        data.append("'").append(item.getBasicUnit()).append("', ");
        // basic_unit_name
        data.append("'").append(item.getBasicUnitName()).append("', ");
        // lot
        data.append("'").append(ObjectUtil.defaultIfNull(item.getLot(), StrUtil.EMPTY)).append("', ");
        // receive_lot
        data.append("'").append(ObjectUtil.defaultIfNull(item.getReceiveLot(), StrUtil.EMPTY)).append("', ");
        // gmt_manufacture
        if (ObjectUtil.isNull(item.getGmtManufacture())) {
            data.append(StrUtil.NULL).append(", ");
        } else {
            data.append("'").append(item.getGmtManufacture()).append("', ");
        }
        // gmt_expire
        if (ObjectUtil.isNull(item.getGmtExpire())) {
            data.append(StrUtil.NULL).append(", ");
        } else {
            data.append("'").append(item.getGmtExpire()).append("', ");
        }
        // gmt_stock
        if (ObjectUtil.isNull(item.getGmtStock())) {
            data.append(StrUtil.NULL).append(", ");
        } else {
            data.append("'").append(item.getGmtStock()).append("', ");
        }
        // supplier
        data.append("'").append(ObjectUtil.defaultIfNull(item.getSupplier(), StrUtil.EMPTY)).append("', ");
        // serial_number
        data.append("'").append(ObjectUtil.defaultIfNull(item.getSerialNumber(), StrUtil.EMPTY)).append("', ");
        // extend_one
        data.append("'").append(ObjectUtil.defaultIfNull(item.getExtendOne(), StrUtil.EMPTY)).append("', ");
        // extend_two
        data.append("'").append(ObjectUtil.defaultIfNull(item.getExtendTwo(), StrUtil.EMPTY)).append("', ");
        // extend_three
        if (ObjectUtil.isNull(item.getExtendThree())) {
            data.append(StrUtil.NULL).append(", ");
        } else {
            data.append("'").append(item.getExtendThree()).append("', ");
        }
        // extend_four
        if (ObjectUtil.isNull(item.getExtendFour())) {
            data.append(StrUtil.NULL).append(", ");
        } else {
            data.append("'").append(item.getExtendFour()).append("', ");
        }
        // extend_five
        if (ObjectUtil.isNull(item.getExtendFive())) {
            data.append(StrUtil.NULL).append(", ");
        } else {
            data.append("'").append(item.getExtendFive()).append("', ");
        }
        // extend_six
        if (ObjectUtil.isNull(item.getExtendSix())) {
            data.append(StrUtil.NULL).append(", ");
        } else {
            data.append("'").append(item.getExtendSix()).append("', ");
        }
        // goods_status
        data.append("'").append(item.getGoodsStatus()).append("', ");
        // goods_status_name
        data.append("'").append(item.getGoodsStatusName()).append("', ");
        // warehouse_code
        data.append("'").append(item.getWarehouseCode()).append("', ");
        // warehouse_space_code
        data.append("'").append(item.getWarehouseSpaceCode()).append("', ");
        // location
        data.append("'").append(item.getLocation()).append("', ");
        // old_location
        data.append("'").append(ObjectUtil.defaultIfNull(item.getOldLocation(), StrUtil.EMPTY)).append("', ");
        // distribution_quantity
        data.append("'").append(item.getDistributionQuantity()).append("', ");
        // picking_quantity
        data.append("'").append(ObjectUtil.defaultIfNull(item.getDistributionQuantity(), BigDecimal.ZERO)).append("', ");
        // not_picking_quantity
        data.append("'").append(ObjectUtil.defaultIfNull(item.getDistributionQuantity(), BigDecimal.ZERO)).append("', ");
        // remarks
        data.append("'").append(ObjectUtil.defaultIfNull(item.getRemarks(), StrUtil.EMPTY)).append("', ");
        // basic_quantity
        data.append("'").append(ObjectUtil.defaultIfNull(item.getBasicQuantity(), BigDecimal.ZERO)).append("', ");
        // inventory_code
        data.append("'").append(item.getInventoryCode()).append("', ");
        // old_inventory_code
        data.append("'").append(ObjectUtil.defaultIfNull(item.getOldInventoryCode(), StrUtil.EMPTY)).append("', ");
        // is_lock
        data.append("'").append(item.getIsLock()).append("', ");
        // status
        data.append("'").append(item.getStatus()).append("', ");
        // inventory_container_bar_code
        data.append("'").append(ObjectUtil.defaultIfNull(item.getInventoryContainerBarCode(), StrUtil.EMPTY)).append("', ");
        // sale_item_code
        data.append("'").append(ObjectUtil.defaultIfNull(item.getSaleItemCode(), StrUtil.EMPTY)).append("', ");
        // is_consignment
        data.append("'").append(item.getIsConsignment()).append("', ");
        // plan_quantity
        data.append("'").append(item.getPlanQuantity()).append("', ");
        // wave_picking_code
        data.append("'").append(item.getWavePickingCode()).append("', ");
        // created_by
        data.append("'").append(createdBy).append("', ");
        // created_name
        data.append("'").append(createdName).append("', ");
        // gmt_created
        data.append("'").append(nowTime).append("', ");
        // modified_by
        data.append("'").append(createdBy).append("', ");
        // modified_name
        data.append("'").append(createdName).append("', ");
        // gmt_modified
        data.append("'").append(nowTime).append("' ");
        data.append(")");
        return data;
    }


    /**
     * 根据波次号和库存code查询发货分配信息
     * @param query
     * @return
     */
    @Override
    public List<DeliveryDistributionVO> queryDistributionsByInventoryCodeAndWavePickingCode(DeliveryDistributionDTO query) {
        //批量拣货查询分配信息
        query.setGroupCode(UserUtil.getBranchCode());
        return iDeliveryDistributionService.queryListByInventoryCode(query, PropertyConst.PICKING);
    }

    /**
     * 通过拣货确认单信息更新拣货单信息
     * @param wavePickingCode
     */
    @Override
    public int updatePickingByPickingRegister(String wavePickingCode) {
        return baseMapper.updatePickingByPickingRegister(wavePickingCode,UserUtil.getBranchCode());
    }

    /**
     * 批量获取拣货单详情
     * @param pickingCodes
     */
    @Override
    public List<PickingVO> batchGetVOWithRegisterByCode(List<String> pickingCodes) {
        List<PickingVO> pickingVOS = Lists.newArrayList();
        for (String pickingCode : pickingCodes) {
            PickingVO pickingVO = this.getVoWithRegisterByCode(pickingCode);
            List<PickingItemWithRegisterVO> pickingItemWithRegisterVOList = pickingVO.getPickingItemWithRegisterVOList();
            //查询当前波次下的作废单以及更新拣货明细数量和总计划拣货数量
            handleInvalid(pickingVO,pickingItemWithRegisterVOList);
            //过滤掉取消后分配数量为0的拣货明细
            pickingItemWithRegisterVOList = pickingItemWithRegisterVOList.stream()
                    .filter(e -> e.getTotalPlanQuantity().compareTo(BigDecimal.ZERO) != 0)
                    .collect(Collectors.toList());
            //相同库位的商品进行合并
            pickingItemWithRegisterVOList = combineGoodsInfoOfSameLocation(pickingItemWithRegisterVOList);
            //对库位进行升序排序
            pickingItemWithRegisterVOList = pickingItemWithRegisterVOList.stream()
                    .sorted(Comparator.comparing(PickingItemWithRegisterVO::getLocationName)).collect(toList());
            pickingVO.setPickingItemWithRegisterVOList(pickingItemWithRegisterVOList);
            pickingVOS.add(pickingVO);
        }
        return pickingVOS;
    }

    private void handleInvalid(PickingVO pickingVO,List<PickingItemWithRegisterVO> pickingItemWithRegisterVOList) {
        String groupCode = pickingVO.getGroupCode();
        String wavePickingCode = pickingVO.getWavePickingCode();
        //查询取消的主单
        List<String> deliveryCodes = iDeliveryService.list(new LambdaQueryWrapper<Delivery>()
                .select(Delivery::getCode)
                .eq(Delivery::getGroupCode,groupCode)
                .eq(Delivery::getWavePickingCode,wavePickingCode)
                .eq(Delivery::getIsCancel, NUM_ONE))
                .stream().map(Delivery::getCode)
                .collect(toList());
        if (IterUtil.isEmpty(deliveryCodes)) {
            return;
        }
        DeliveryItemDTO deliveryItemDTO = new DeliveryItemDTO();
        deliveryItemDTO.setGroupCode(groupCode);
        deliveryItemDTO.setDeliveryCodes(deliveryCodes);
        //按商品分组，插叙取消订单的分配数量
        List<DeliveryItemVO> deliveryItemVOS = iDeliveryItemService.querySumDistributionQuantity(deliveryItemDTO);
        Map<String,DeliveryItemVO> deliveryItemVOMap = deliveryItemVOS.stream().collect(Collectors.toMap(DeliveryItemVO::getGoodCode, e -> e));
        //汇总取消订单总的分配数量
        BigDecimal canceledDistributionQuantity = deliveryItemVOS.stream().map(DeliveryItemVO::getDistributionQuantity).reduce(BigDecimal.ZERO, BigDecimal::add);
        BigDecimal planQuantity = pickingVO.getPlanQuantity().subtract(canceledDistributionQuantity);
        pickingVO.setPlanQuantity(planQuantity);
        //更新明细的汇总数量,一个商品如果有多个明细，更新到第一个上
        Map<String, List<PickingItemWithRegisterVO>> mapList = pickingItemWithRegisterVOList.stream()
                .collect(Collectors.groupingBy(PickingItemWithRegisterVO::getGoodCode));
        Map<String,PickingItemWithRegisterVO> pickingItemWithRegisterVOMap = pickingItemWithRegisterVOList.stream()
                .collect(Collectors.toMap(PickingItemWithRegisterVO::getCode, e -> e));
        for (Map.Entry<String,List<PickingItemWithRegisterVO>> entry : mapList.entrySet()) {
            DeliveryItemVO deliveryItemVO = MapUtil.get(deliveryItemVOMap, entry.getKey(), DeliveryItemVO.class);

            if (null != deliveryItemVO && null != deliveryItemVO.getDistributionQuantity()) {
                PickingItemWithRegisterVO pickingItemWithRegisterVO = MapUtil.get(pickingItemWithRegisterVOMap, entry.getValue().get(NUM_ZERO).getCode(), PickingItemWithRegisterVO.class);
                BigDecimal goodCodeDistributionQuantity = deliveryItemVO.getDistributionQuantity();
                BigDecimal goodCodePlanQuantity = pickingItemWithRegisterVO.getPlanQuantity().subtract(goodCodeDistributionQuantity);
                pickingItemWithRegisterVO.setPlanQuantity(goodCodePlanQuantity);
                pickingItemWithRegisterVO.setDistributionQuantity(goodCodePlanQuantity);
                pickingItemWithRegisterVO.setTotalPlanQuantity(goodCodePlanQuantity);
            }
        }

    }

    /**
     * 查询各订单标识对应的任务数量
     */
    @Override
    public List<PickingVO> queryTaskQuantityGroupByOrderTag() {
        return baseMapper.queryTaskQuantityGroupByOrderTag();
    }

    /**
     * 查询拣货单信息
     */
    @Override
    public PickingVO getPickingInfo(String pickingCode) {
        Picking picking =  this.getOne(new QueryWrapper<Picking>().lambda().eq(Picking::getCode, pickingCode).eq(Picking::getGroupCode,UserUtil.getBranchCode()));
        PickingVO pickingVO = new PickingVO();
        BeanUtil.copyProperties(picking,pickingVO);
        pickingVO.setLogisticsName(LogisticsUtils.translate(pickingVO.getLogisticsCode()));
        List<Delivery> deliveryList = iDeliveryService.list(new QueryWrapper<Delivery>().lambda()
                        .eq(Delivery::getGroupCode,UserUtil.getBranchCode())
                        .eq(Delivery::getWavePickingCode, picking.getWavePickingCode())
                );
        pickingVO.setOrderQuantity(new BigDecimal(deliveryList.size()));
        return pickingVO;
    }

    @Override
    public List<DeliveryVO> printExpress(String pickingCode) {
        Picking picking = getOne(new QueryWrapper<Picking>().lambda().eq(Picking::getCode, pickingCode) .eq(Picking::getGroupCode,UserUtil.getBranchCode()));
        if (picking == null) {
            throw new ServiceException("拣货单不存在，请确认");
        }
       List<Delivery> deliveries =  iDeliveryService.list(new LambdaQueryWrapper<Delivery>()
               .select(Delivery::getCode)
               .eq(Delivery::getIsCancel, NUM_ZERO)
               .eq(Delivery::getWavePickingCode,picking.getWavePickingCode())
               .eq(Delivery::getGroupCode,UserUtil.getBranchCode()));
        List<String > deliveryCodes = deliveries.stream().map(Delivery::getCode).collect(Collectors.toList());
        if (deliveryCodes.size() <= 0) {
            List<Delivery> deliveriesS =  iDeliveryService.list(new LambdaQueryWrapper<Delivery>()
                    .select(Delivery::getCode)
                    .eq(Delivery::getIsCancel, NUM_ONE)
                    .eq(Delivery::getWavePickingCode,picking.getWavePickingCode())
                    .eq(Delivery::getGroupCode,UserUtil.getBranchCode()));
            throw new ServiceException(HAVE + deliveriesS.size() + ITEM + ORDER + CANCELED);
        }
        return iDeliveryService.printExpress(deliveryCodes);
    }

    /**
     * 波次打印面单
     *
     * @param wavePickingCode
     * @return
     */
    @Override
    public List<DeliveryVO> wavePrintWaybill(String wavePickingCode){
        if (StrUtil.isEmpty(wavePickingCode)) {
            throw new ServiceException("波次号不能为空");
        }
        String groupCode = UserUtil.getBranchCode();
        Picking picking = this.getOne(new LambdaQueryWrapper<Picking>().select(Picking::getOrderTag,Picking::getReview,Picking::getStatus)
                .eq(Picking::getWavePickingCode, wavePickingCode)
                .eq(Picking::getGroupCode,groupCode)
        );
        if (picking == null) {
            throw new ServiceException("波次号【" + wavePickingCode + "】没有查到拣货单信息,请检查波次号是否正确");
        }
        if (!OrderTagEnum.MOST_POPULAR_ITEM.getValue().equals(picking.getOrderTag())) {
            throw new ServiceException("波次号【" + wavePickingCode + "】查到的拣货单订单类型不是爆品，不能波次打印");
        }
        //判断是否跳过复核
        if (0 != picking.getReview()) {
            throw new ServiceException("波次号【" + wavePickingCode + "】查到的拣货单需要复核，不能波次打印");
        }
        if (!PickingStatusEnum.FINISH_PICK.getValue().equals(picking.getStatus())) {
            throw new ServiceException("波次号【" + wavePickingCode + "】查到的拣货单未拣货完成，不能波次打印");
        }
        List<Delivery> deliveries =  iDeliveryService.list(new LambdaQueryWrapper<Delivery>()
                .select(Delivery::getCode)
                .eq(Delivery::getIsCancel, NUM_ZERO)
                .eq(Delivery::getWavePickingCode,wavePickingCode)
                .eq(Delivery::getGroupCode,groupCode));
        List<String > deliveryCodes = deliveries.stream().map(Delivery::getCode).collect(Collectors.toList());
        if (deliveryCodes.size() <= 0) {
            List<Delivery> deliveriesS =  iDeliveryService.list(new LambdaQueryWrapper<Delivery>()
                    .select(Delivery::getCode)
                    .eq(Delivery::getIsCancel, NUM_ONE)
                    .eq(Delivery::getWavePickingCode,wavePickingCode)
                    .eq(Delivery::getGroupCode,groupCode));
            throw new ServiceException(HAVE + deliveriesS.size() + ITEM + ORDER + CANCELED);
        }
        return iDeliveryService.printExpress(deliveryCodes);
    };
    /**
     * 更新拣货单取消数量和状态
     * 取消数量+拣货数量+缺货数量=分配数量的时候拣货单状态为完成
     *
     * @param pickingCode
     * @param cancelQuantity
     * @return boolean
     * @author liuxr
     * @date 2021/5/17 16:33
     */
    @Override
    public boolean updateCancelQuantityAndStatusByCode(String pickingCode, BigDecimal cancelQuantity) {
        return this.baseMapper.updateCancelQuantityAndStatusByCode(pickingCode,cancelQuantity);
    }

    /**
     * 拣货确认更新拣货单拣货完成
     *
     * @param pickingCode
     * @return int
     * @author liuxr
     * @date 2021/5/27 9:02
     */
    @Override
    public int updateCheckFinsh(String pickingCode,String groupCode) {
        return  this.baseMapper.updateCheckFinsh(pickingCode,groupCode);
    }

    /**
     * 波次拣货缺货登记，更新拣货单登记明细拣货单明细缺货数量、拣货数量、状态
     *
     * @param wavePickingCode  波次号
     * @param oldInventoryCode 原始库存标识
     * @param refundQuantity   缺货数量
     * @author liuxr
     * @date 2021/6/8 9:10
     */
    @Override
    public void updateItemsOutStockQuantity(String wavePickingCode, String oldInventoryCode, BigDecimal refundQuantity,String groupCode) {
        this.baseMapper.updateItemsOutStockQuantity(wavePickingCode,oldInventoryCode,refundQuantity,groupCode);
    }

    /**
     * 查询预打包单拣货信息
     * @author rfwang
     * @date 2021年7月9日
     * @param pickingCode 拣货单号
     */
    @Override
    public PickingVO getPrepackagedPickingInfo(String pickingCode){
        PickingVO pickingVO = new PickingVO();
        List<PickingItemVO> pickingItemVOList = Lists.newArrayList();
        //1.设置预打包拣货库位，默认YDKW
        pickingVO.setLocationSerial(CommonConst.YDKW);
        Picking picking = this.getOne(new LambdaQueryWrapper<Picking>().eq(Picking::getCode, pickingCode).eq(Picking::getGroupCode,UserUtil.getBranchCode()));
        BeanUtil.copyProperties(picking,pickingVO);
        List<PickingItem> pickingItems = iPickingItemService.list(new LambdaQueryWrapper<PickingItem>()
                .eq(PickingItem::getGroupCode,UserUtil.getBranchCode())
                .eq(PickingItem::getPickingCode, pickingCode));

        List<Delivery> deliveryList = iDeliveryService.list(new LambdaQueryWrapper<Delivery>()
                .select(Delivery::getCode)
                .eq(Delivery::getGroupCode,UserUtil.getBranchCode())
                .eq(Delivery::getWavePickingCode, picking.getWavePickingCode()));

        Map<String,Object> deliveryItemMap = iDeliveryItemService.list(new LambdaQueryWrapper<DeliveryItem>()
                .eq(DeliveryItem::getGroupCode,UserUtil.getBranchCode())
                .select(DeliveryItem::getGoodCode,
                        DeliveryItem::getDistributionQuantity)
                .eq(DeliveryItem::getDeliveryCode, deliveryList.get(NUM_ZERO).getCode())).stream()
                .collect(Collectors.toMap(e->e.getGoodCode(),e->e.getDistributionQuantity()));
        //待拣包裹数
        Integer planPackQuantity = deliveryList.size();
        pickingItemVOList = pickingItems.stream().map(info -> {
            PickingItemVO itemVO = new PickingItemVO();
            itemVO.setDistributionQuantity(MapUtil.get(deliveryItemMap, itemVO.getGoodCode(), BigDecimal.class));
            BeanUtil.copyProperties(info, itemVO);
            return itemVO;
        }).collect(Collectors.toList());
        pickingVO.setPickingItemVOList(pickingItemVOList);
        pickingVO.setPlanPackQuantity(planPackQuantity);
        return pickingVO;
    }

    /**
     * pda预打包确认
     *
     * @param pickingCode 拣货单号
     * @author rfwang
     * @date 2021年7月9日
     */
    @Override
    public PdaSwitchPageVO pdaPrepackagedCheck(String pickingCode){
        PdaSwitchPageVO pdaSwitchPageVO = new PdaSwitchPageVO();
        //不缺货的情况下，走该方法
        //1.更新拣货主单、拣货明细、拣货确认表的拣货数量和状态
        Picking picking = this.getOne(new LambdaQueryWrapper<Picking>().eq(Picking::getCode, pickingCode).eq(Picking::getGroupCode,UserUtil.getBranchCode()));
        BigDecimal pickingQuantity = picking.getPlanQuantity();
        if (null == picking) {
            throw new ServiceException("拣货单不存在，请确认。");
        }
        // “待拣货”、“拣货中”状态可以拣货确认，其他状态不允许确认
        if (StrUtil.equals(picking.getStatus(), PickingStatusEnum.FINISH_PICK.getValue())) {
            throw new ServiceException("拣货单已经拣货完成，请确认。");
        }
        updatePicking(picking);
        //2.更新订单主单、订单明细、订单分配表的拣货数量和状态
        updateDelivery(picking);

        //3.更新任务信息
        // 更新任务已完成作业量，作业状态在占用作业时就已更新，所以不需要再更新
        iWmsTaskService.update(new UpdateWrapper<WmsTask>().lambda().eq(WmsTask::getSourceCode, picking.getCode())
                .eq(WmsTask::getGroupCode,UserUtil.getBranchCode())
                .setSql(" job_quantity = job_quantity + " + pickingQuantity)
                .set(WmsTask::getJobStatus,WmsTaskJobStatusEnum.COMPLETED));

        //4.生成出库单并判断是否出库
        genOutbound(pickingCode);

        //5.调整pda跳转页面
        // 单据完成，可跳转到拣货列表页面
        pdaSwitchPageVO.setToPage(NUM_THREE);
        return pdaSwitchPageVO;
    }

    @Transactional(rollbackFor = {Exception.class})
    public void updatePicking(Picking picking) {
        String pickingCode = picking.getCode();
        //更新拣货主单信息
        this.update(new LambdaUpdateWrapper<Picking>().eq(Picking::getCode, pickingCode)
                .eq(Picking::getGroupCode,UserUtil.getBranchCode())
                .setSql("picking_quantity = plan_quantity")
                .set(Picking::getNotPickingQuantity, BigDecimal.ZERO)
                .set(Picking::getStatus, PickingStatusEnum.FINISH_PICK.getValue()));
        //更新拣货明细信息
        iPickingItemService.update(new LambdaUpdateWrapper<PickingItem>()
                .eq(PickingItem::getGroupCode,UserUtil.getBranchCode())
                .eq(PickingItem::getPickingCode, pickingCode)
                .setSql("picking_quantity = plan_quantity")
                .set(PickingItem::getStatus, PickingStatusEnum.FINISH_PICK.getValue()));
        //更新拣货确认单信息
        iPickingRegisterService.update(new LambdaUpdateWrapper<PickingRegister>()
                .eq(PickingRegister::getGroupCode,UserUtil.getBranchCode())
                .eq(PickingRegister::getPickingCode, pickingCode)
                .setSql("picking_quantity = plan_quantity")
                .set(PickingRegister::getNotPickingQuantity, BigDecimal.ZERO)
                .set(PickingRegister::getStatus, PickingStatusEnum.FINISH_PICK.getValue()));
    }

    @Transactional(rollbackFor = {Exception.class})
    public void updateDelivery(Picking picking){
        String wavePickingCode = picking.getWavePickingCode();
        //更新订单主单信息
        iDeliveryService.update(new LambdaUpdateWrapper<Delivery>()
                .eq(Delivery::getGroupCode,UserUtil.getBranchCode())
                .eq(Delivery::getWavePickingCode, wavePickingCode)
                .setSql("pick_num = distribution_num")
                .set(Delivery::getStatus, DeliveryStatusEnum.PICKED.getValue()));
        //更新订单明细的拣货数量
        iDeliveryItemService.update(new LambdaUpdateWrapper<DeliveryItem>()
                .eq(DeliveryItem::getGroupCode,UserUtil.getBranchCode())
                .eq(DeliveryItem::getWavePickingCode,wavePickingCode)
                .setSql("picking_quantity = distribution_quantity")
                .set(DeliveryItem::getStatus, DeliveryStatusEnum.PICKED.getValue()));
        //更新订单分配的拣货数量
        iDeliveryDistributionService.update(new LambdaUpdateWrapper<DeliveryDistribution>()
                .eq(DeliveryDistribution::getGroupCode,UserUtil.getBranchCode())
                .eq(DeliveryDistribution::getWavePickingCode,wavePickingCode)
                .setSql("picking_quantity = distribution_quantity")
                .set(DeliveryDistribution::getStatus, DeliveryStatusEnum.PICKED.getValue()));
    }

    @Transactional(rollbackFor = {Exception.class})
    public void genOutbound(String pickingCode) {
        //用更新后的订单生成出库单
        Picking picking = this.getOne(new LambdaQueryWrapper<Picking>().eq(Picking::getCode, pickingCode).eq(Picking::getGroupCode,UserUtil.getBranchCode()));
        wavePickingConfirmationService.genOutboundInfoByDelivery(picking, picking.getReview());
        log.info("拣货确认：【step3】，更新订单信息");
        pickingConfirmationService.pickConfirmationUpdateDeliveryInfo(picking);
        log.info("拣货确认：【step4】，判断是否跳过复核");
        if (picking.getReview() == 0) {
            RLock ol = null;
            try {
                ol = redissonClient.getLock(getOutConfirmLockKey(picking.getCode()));
                if (ol.tryLock()) {
                    log.info("波次拣货【step5】：，拣货单跳过复核");
                    log.info("波次拣货【step5-1】：，自动复核");
                    outCheckService.autoReview(picking.getWavePickingCode(),UserUtil.getBranchCode());

                    log.info("波次拣货【step5-2】：自动出库");
                    outCheckService.autoOutConfirmation(picking.getWavePickingCode(),StrUtil.EMPTY, NUM_ZERO,NUM_ONE,UserUtil.getBranchCode());

                    log.info("波次拣货确认，出库单复核：【step6】，异步释放容器");
                    outCheckService.releaseContainer(picking.getWavePickingCode(),UserUtil.getBranchCode());
                } else {
                    log.error("拣货单【{}】，拣货确认获得锁失败", picking.getCode());
                }
            }  catch (Exception e) {
                log.error(StrConsts.CATCH_MESSAGE, e);
                throw new ServiceException(e.getMessage());
            } finally {
                try {
                    if (null != ol && ol.isLocked()) {
                        ol.unlock();
                    }
                } catch (Exception e) {
                    log.error("出库确认释放锁失败！！", e);
                }
            }
        }
    }

    /**
     * pda预打包缺货登记
     *
     * @param wavePickingCode 波次号
     * @param deliveryDTOList 订单列表
     * @author rfwang
     * @date 2021年7月9日
     */
    @Override
    public PdaSwitchPageVO prepackagedStockout(String wavePickingCode, List<DeliveryDTO> deliveryDTOList,Integer planPickQuantity,Integer pickQuantity){
        PdaSwitchPageVO pdaSwitchPageVO = new PdaSwitchPageVO();
        //缺货的情况下，走该方法
        //1.更新拣货主单、拣货明细、拣货确认表的拣货数量和状态
        Picking picking = this.getOne(new LambdaQueryWrapper<Picking>().eq(Picking::getWavePickingCode, wavePickingCode).eq(Picking::getGroupCode,UserUtil.getBranchCode()).eq(Picking::getGroupCode,UserUtil.getBranchCode()));
        BigDecimal pickingQuantity = picking.getPlanQuantity();
        if (null == picking) {
            throw new ServiceException("拣货单不存在，请确认。");
        }
        // “待拣货”、“拣货中”状态可以拣货确认，其他状态不允许确认
        if (StrUtil.equals(picking.getStatus(), PickingStatusEnum.FINISH_PICK.getValue())) {
            throw new ServiceException("拣货单已经拣货完成，请确认。");
        }
        updatePrepackagedPicking(wavePickingCode, planPickQuantity, pickQuantity);
        //2.更新订单主单、订单明细、订单分配表的拣货数量和状态
        updatePrepackagedDelivery(deliveryDTOList);

        //3.更新任务信息
        // 更新任务已完成作业量，作业状态在占用作业时就已更新，所以不需要再更新
        iWmsTaskService.update(new UpdateWrapper<WmsTask>().lambda().eq(WmsTask::getSourceCode, picking.getCode()).eq(WmsTask::getGroupCode,UserUtil.getBranchCode()).eq(WmsTask::getGroupCode,UserUtil.getBranchCode())
                .setSql(" job_quantity = job_quantity + " + pickingQuantity)
                .set(WmsTask::getJobStatus,WmsTaskJobStatusEnum.COMPLETED));

        //4.生成出库单并判断是否出库
        genOutbound(picking.getCode());

        //5.调整pda跳转页面
        // 单据完成，可跳转到拣货列表页面
        pdaSwitchPageVO.setToPage(NUM_THREE);
        return pdaSwitchPageVO;
    }


    /**
     * 预打包缺货按比例更新拣货主单、拣货明细、拣货登记表数量
     *
     * @param wavePickingCode  波次号
     * @param planPickQuantity 计划拣货包裹数
     *                         param pickQuantity 实际拣货包裹数
     * @author rfwang
     * @date 2021年7月9日
     */
    @Override
    @Transactional(rollbackFor = {Exception.class})
    public void updatePrepackagedPicking(String wavePickingCode, Integer planPickQuantity, Integer pickQuantity){
        String branchCode = UserUtil.getBranchCode();
        this.baseMapper.updatePrepackagedPicking(wavePickingCode,planPickQuantity,pickQuantity,branchCode);
    }

    @Transactional(rollbackFor = {Exception.class})
    public void updatePrepackagedDelivery( List<DeliveryDTO> deliveryDTOList) {
        //不缺货的订单
        List<String> deliveryCodes = deliveryDTOList.stream().filter(e -> e.getPickNum().compareTo(BigDecimal.ONE) == 0)
                .map(DeliveryDTO::getCode).collect(Collectors.toList());
        //完全缺货的订单
        List<String> stockoutDeliveryCodes = deliveryDTOList.stream().filter(e -> e.getPickNum().compareTo(BigDecimal.ZERO) == 0)
                .map(DeliveryDTO::getCode).collect(Collectors.toList());
        //完全缺货
        if (IterUtil.isNotEmpty(stockoutDeliveryCodes)) {
            //抹除订单、订单明细的波次号等信息、删除分配单信息
            iDeliveryService.update(new UpdateWrapper<Delivery>().lambda().eq(Delivery::getGroupCode,UserUtil.getBranchCode()).in(Delivery::getCode, stockoutDeliveryCodes).set(Delivery::getPickNum, BigDecimal.ZERO).set(Delivery::getWavePickingCode, StrUtil.EMPTY).set(Delivery::getPickStockOutNum,BigDecimal.ZERO).set(Delivery::getStatus,DeliveryStatusEnum.UN_DISTRIBUTION.getValue()).set(Delivery::getRecheckQuantity,BigDecimal.ZERO).set(Delivery::getRecheckRefundQuantity,BigDecimal.ZERO).set(Delivery::getDistributionNum,BigDecimal.ZERO));
            iDeliveryItemService.update(new UpdateWrapper<DeliveryItem>().lambda() .eq(DeliveryItem::getGroupCode,UserUtil.getBranchCode()).in(DeliveryItem::getDeliveryCode, stockoutDeliveryCodes).set(DeliveryItem::getPickingQuantity, BigDecimal.ZERO).set(DeliveryItem::getPickStockOutNum, BigDecimal.ZERO).set(DeliveryItem::getWavePickingCode,StrUtil.EMPTY).set(DeliveryItem::getDistributionQuantity,BigDecimal.ZERO).set(DeliveryItem::getStatus,DeliveryStatusEnum.ADD.getValue()).set(DeliveryItem::getRecheckQuantity,BigDecimal.ZERO).set(DeliveryItem::getDistributionQuantity,BigDecimal.ZERO));
            iDeliveryDistributionService.remove(new QueryWrapper<DeliveryDistribution>().lambda() .eq(DeliveryDistribution::getGroupCode,UserUtil.getBranchCode()).in(DeliveryDistribution::getDeliveryCode, stockoutDeliveryCodes));
        }
        if (IterUtil.isNotEmpty(stockoutDeliveryCodes)) {
            //更新订单主单信息
            iDeliveryService.update(new LambdaUpdateWrapper<Delivery>()
                    .in(Delivery::getCode, deliveryCodes)
                    .eq(Delivery::getGroupCode,UserUtil.getBranchCode())
                    .setSql("pick_num = distribution_num")
                    .set(Delivery::getStatus, DeliveryStatusEnum.PICKED.getValue()));
            //更新订单明细的拣货数量
            iDeliveryItemService.update(new LambdaUpdateWrapper<DeliveryItem>()
                    .eq(DeliveryItem::getGroupCode,UserUtil.getBranchCode())
                    .in(DeliveryItem::getDeliveryCode,deliveryCodes)
                    .setSql("picking_quantity = distribution_quantity")
                    .set(DeliveryItem::getStatus, DeliveryStatusEnum.PICKED.getValue()));
            //更新订单分配的拣货数量
            iDeliveryDistributionService.update(new LambdaUpdateWrapper<DeliveryDistribution>()
                    .eq(DeliveryDistribution::getGroupCode,UserUtil.getBranchCode())
                    .in(DeliveryDistribution::getDeliveryCode,deliveryCodes)
                    .setSql("picking_quantity = distribution_quantity")
                    .set(DeliveryDistribution::getStatus, DeliveryStatusEnum.PICKED.getValue()));
        }
    }

    /**
     * 更新拣货单出库下发状态
     * @param wavePickingCode 波次号
     */
    @Override
    public void updateOutboundIssueStatus(String wavePickingCode, String groupCode){
        update(new UpdateWrapper<Picking>().lambda()
                .eq(Picking::getGroupCode,groupCode)
                .eq(Picking::getWavePickingCode, wavePickingCode)
                .set(Picking::getOutboundIsIssue, IssueStatusEnum.ISSUED.getValue()));

        iOutboundService.update(new LambdaUpdateWrapper<Outbound>()
                .eq(Outbound::getGroupCode, groupCode)
                .eq(Outbound::getWavePickingCode, wavePickingCode)
                .set(Outbound::getIssue, IssueStatusEnum.ISSUED.getValue()));
    }

    /**
     * 批量保存拣货单，拣货明细，拣货登记信息
     * @param pickingList
     * @param pickingItemList
     * @param pickingRegisterList
     */
    @Transactional(rollbackFor = {Exception.class})
    @Override
    public void savePickingsInfo( List<Picking> pickingList, List<PickingItem> pickingItemList, List<PickingRegister> pickingRegisterList){
        log.info("生成波次，创建拣货单");
        this.saveBatch(pickingList);
        log.info("生成波次，创建拣货明细");
        this.iPickingItemService.saveBatch(pickingItemList);
        log.info("生成波次，创建拣货登记明细");
        this.iPickingRegisterService.saveBatch(pickingRegisterList);
    }

    /**
     * 批量更新总检单打印次数
     * @param codes
     */
    @Override
    @Transactional(rollbackFor = {Exception.class})
    public void batchGeneralPickCount(List<String> codes){
        String branchCode = UserUtil.getBranchCode();
        this.baseMapper.batchGeneralPickCount(codes,branchCode);

    }

    /**
     * 批量更新拣货单（序号）打印次数增加
     * @param codes
     */
    @Override
    @Transactional(rollbackFor = {Exception.class})
    public void batchPickSortCount(List<String> codes) {
        String branchCode = UserUtil.getBranchCode();
        this.baseMapper.batchPickSortCount(codes,branchCode);
    }

    /**
     * 拣货单容器号赋值及锁定容器号
     * @param pickingCode
     * @param containerBarCode
     */
    @Override
    @Transactional(rollbackFor = {Exception.class})
    public void updatePickingContanerBarCode(String pickingCode,String containerBarCode){
        this.update(new LambdaUpdateWrapper<Picking>().eq(Picking::getCode, pickingCode)
                .set(Picking::getContainerBarCode, containerBarCode)
                .eq(Picking::getGroupCode,UserUtil.getBranchCode())
                .set(Picking::getOccupy, NUM_ONE)
        );
        //占用容器
        iContainerService.wavePickingOccupyContainer(pickingCode, containerBarCode);
    }
    /**
     * 根据波次号更新打印次数
     * @param wavePickingCode
     */
    @Override
    @Transactional(rollbackFor = {Exception.class})
    public void updatePrintCount(String wavePickingCode){
        String branchCode = UserUtil.getBranchCode();
        //更新拣货单的面单打印次数
        this.baseMapper.updatePrintCount(wavePickingCode,branchCode);
        //更新订单的面单打印次数
        iDeliveryService.updatePrintCountByWavePickingCode(wavePickingCode,branchCode);
    }
    /**
     * 拣货确认分布式锁key
     *
     * @param code 拣货单号
     * @return
     */

    /**
     * 波次拣货，释放容器号
     * @param wavePickingCode
     */
    @Override
    @Async
    public void releaseContainer(String wavePickingCode,String groupCode){
        //如果拣货单没有绑定容器，直接结束
        int occupyContainer = this.count(new LambdaQueryWrapper<Picking>()
                .eq(Picking::getWavePickingCode, wavePickingCode)
                .eq(Picking::getGroupCode,groupCode)
        );
        if (occupyContainer == 0) {
            return;
        }
        //占用波次容器的状态，待检货，拣货中，待复核，复核中
        List<String> statuses = Lists.newArrayList(DeliveryStatusEnum.UN_PICKING.getValue(), DeliveryStatusEnum.PICKING.getValue(),
                DeliveryStatusEnum.PICKED.getValue(), DeliveryStatusEnum.UN_RECHECK.getValue(), DeliveryStatusEnum.RECHECKING.getValue());
        int beforeDeliveryNum = this.iDeliveryService.count(new LambdaQueryWrapper<Delivery>()
                .eq(Delivery::getWavePickingCode, wavePickingCode)
                .eq(Delivery::getGroupCode,groupCode)
                .eq(Delivery::getIsCancel, NUM_ZERO)
                .in(Delivery::getStatus, statuses)
        );
        if (beforeDeliveryNum == 0) {
            //波次下的订单都复核完成时，解除波次和容器绑定
            boolean release = this.update(new LambdaUpdateWrapper<Picking>()
                    .eq(Picking::getWavePickingCode, wavePickingCode)
                    .eq(Picking::getGroupCode,groupCode)
                    .set(Picking::getOccupy, NUM_ZERO));

            if (release) {
                Picking picking = this.getOne(new LambdaQueryWrapper<Picking>().select(Picking::getContainerBarCode)
                        .eq(Picking::getWavePickingCode, wavePickingCode)
                        .eq(Picking::getGroupCode,groupCode)
                );
                iContainerService.releaseContainer(picking.getContainerBarCode(),groupCode);
            }
        }

    }

    /**
     * 获取拣货单详情(包含拣货序号)
     * @param code
     */
    @Override
    public PickingVO getItemWithOrderNum(String code) {
        String groupCode = UserUtil.getBranchCode();
        PickingVO pickingVO = new PickingVO();
        Picking entity = this.getOne(new QueryWrapper<Picking>().lambda()
                .eq(Picking::getCode, code)
                .eq(Picking::getGroupCode, groupCode)
        );
        String wavePickingCode = entity.getWavePickingCode();
        //查询波次下的订单数量
        List<String> deliveryCodes = iDeliveryService.list(new LambdaQueryWrapper<Delivery>().select(Delivery::getCode)
                .eq(Delivery::getWavePickingCode, wavePickingCode)
                .eq(Delivery::getGroupCode,groupCode)
                .eq(Delivery::getIsCancel, NUM_ZERO)
        ).stream().map(Delivery::getCode).collect(Collectors.toList());
        pickingVO.setDeliveryNum(deliveryCodes.size());
        BeanUtil.copyProperties(entity, pickingVO);
        DeliveryDistributionDTO deliveryDistributionDTO = new DeliveryDistributionDTO();
        PickingItemDTO pickingItemDTO = new PickingItemDTO();
        pickingItemDTO.setPickingCode(entity.getCode());
        pickingItemDTO.setPageSize(NUM_NINETY_NINE_THOUSAND_NINE_HUNDRED_NINETY_NINE);
        pickingItemDTO.setGroupCode(groupCode);
        //此方法只是单表查询wms_picking_item返回PickingItemWithRegisterVO类
        List<PickingItemWithRegisterVO> pickingItemWithRegisterVOS = iPickingItemService.queryItemReturnRegisterList(pickingItemDTO);
        //合并同一商品在相同库位不同库存的商品
        pickingItemWithRegisterVOS = combineGoodsInfoOfSameLocation(pickingItemWithRegisterVOS);
        deliveryDistributionDTO.setWavePickingCode(wavePickingCode);
        List<String> locations = pickingItemWithRegisterVOS.stream().map(PickingItemWithRegisterVO::getLocation).collect(Collectors.toList());
        deliveryDistributionDTO.setLocations(locations);
        deliveryDistributionDTO.setGroupCode(groupCode);
        deliveryDistributionDTO.setDeliveryCodes(deliveryCodes);
        deliveryDistributionDTO.setPageSize(NUM_NINETY_NINE_THOUSAND_NINE_HUNDRED_NINETY_NINE);
        //获取订单顺序及数量集合
        List<DeliveryDistributionVO> deliveryDistributionVOList = iDeliveryDistributionService.queryList(deliveryDistributionDTO);
        Map<String, String> deliveryOrderMap = Maps.newHashMap();
        //按照库位加商品进行分组
        Map<String,List<DeliveryDistributionVO>> listMap = deliveryDistributionVOList.stream().collect(Collectors.groupingBy(e -> e.getLocation() + "_" + e.getGoodCode()));

        for (Map.Entry<String, List<DeliveryDistributionVO>> entry : listMap.entrySet()) {
            String comDeliveryOrderNum = StrUtil.EMPTY;
            for (DeliveryDistributionVO e : entry.getValue()) {
                comDeliveryOrderNum = comDeliveryOrderNum + geneDeliveryOrderNum(e.getOrderNum(), e.getDistributionQuantity());
            }
            //处理最后一个逗号
            deliveryOrderMap.put(entry.getKey(), comDeliveryOrderNum.substring(0, comDeliveryOrderNum.length() - 2));
        }
        // 翻译
        pickingVO.setWarehouseName(warehouseCache.translate(pickingVO.getWarehouseCode()));
        pickingVO.setCustomerName(customerCache.translate(pickingVO.getCustomerCode()));
        pickingVO.setGroupName(partyGroupCache.translate(pickingVO.getGroupCode()));


        //判断当前波次下是否有取消订单，如果有的话，需要对拣货主单和明细的一些数量重新赋值
        int cancelDeliveryCount = iDeliveryService.count(new LambdaQueryWrapper<Delivery>().select(Delivery::getCode)
                .eq(Delivery::getWavePickingCode, wavePickingCode)
                .eq(Delivery::getGroupCode, groupCode)
                .eq(Delivery::getIsCancel, NUM_ONE));

        if (cancelDeliveryCount > 0) {
            //判断是否全部取消
            int notCancelDeliveryCount = iDeliveryService.count(new LambdaQueryWrapper<Delivery>().select(Delivery::getCode)
                    .eq(Delivery::getWavePickingCode, wavePickingCode)
                    .eq(Delivery::getGroupCode, groupCode)
                    .eq(Delivery::getIsCancel, NUM_ZERO));
            if (notCancelDeliveryCount == 0) {
                pickingVO.setGoodsSortQuantity(BigDecimal.ZERO);
                pickingVO.setPlanQuantity(BigDecimal.ZERO);
                return pickingVO;
            }
            //用分配明细的商品统计商品数量，以防订单有取消，显示商品种类数量不准
            Map<String,List<DeliveryDistributionVO>> listGoodsMap = deliveryDistributionVOList.stream().collect(Collectors.groupingBy(DeliveryDistributionVO::getGoodCode));
            int goodsSortQuantity = listGoodsMap.keySet().size();
            BigDecimal totalPlanQuantity = deliveryDistributionVOList.stream().map(DeliveryDistributionVO::getDistributionQuantity).reduce(BigDecimal.ZERO, BigDecimal::add);
            pickingVO.setGoodsSortQuantity(new BigDecimal(goodsSortQuantity));
            pickingVO.setPlanQuantity(totalPlanQuantity);
            pickingItemWithRegisterVOS.stream().forEach(e -> {
                e.setDeliveryOrderNum(MapUtil.get(deliveryOrderMap, e.getLocation() + "_" + e.getGoodCode(), String.class));
                e.setLocationName(locationCache.translate(e.getLocation()));
                e.setStatusText(PickingRegisterStatusEnum.translate(e.getStatus()));
                List<DeliveryDistributionVO> deliveryDistributionVOS = MapUtil.get(listMap, e.getLocation() + "_" + e.getGoodCode(), List.class);
                if (IterUtil.isNotEmpty(deliveryDistributionVOS)) {
                    BigDecimal itemPlanQuantity = deliveryDistributionVOS.stream().map(DeliveryDistributionVO::getDistributionQuantity).reduce(BigDecimal.ZERO, BigDecimal::add);
                    e.setDistributionQuantity(itemPlanQuantity);
                } else {
                    e.setDistributionQuantity(BigDecimal.ZERO);
                }
            });
            //过滤掉完全取消的商品
            pickingItemWithRegisterVOS = pickingItemWithRegisterVOS.stream().filter(e -> e.getDistributionQuantity().compareTo(BigDecimal.ZERO) > 0).collect(Collectors.toList());
        } else {
            pickingItemWithRegisterVOS.stream().forEach(e -> {
                e.setDeliveryOrderNum(MapUtil.get(deliveryOrderMap, e.getLocation() + "_" + e.getGoodCode(), String.class));
                e.setLocationName(locationCache.translate(e.getLocation()));
                e.setStatusText(PickingRegisterStatusEnum.translate(e.getStatus()));
            });
        }

        //对库位进行升序排序
        pickingItemWithRegisterVOS = pickingItemWithRegisterVOS.stream()
                .sorted(Comparator.comparing(PickingItemWithRegisterVO::getLocationName)).collect(toList());
        pickingVO.setPickingItemWithRegisterVOList(pickingItemWithRegisterVOS);

        return pickingVO;
    }


    /**
     * 波次打印运单
     *
     * @param wavePickingCode
     * @return
     */
    @Override
    public  List<Delivery> wavePrinDelivery(String wavePickingCode){
        if (StrUtil.isEmpty(wavePickingCode)) {
            throw new ServiceException("波次号不能为空");
        }
        String groupCode = UserUtil.getBranchCode();
        Picking picking = this.getOne(new LambdaQueryWrapper<Picking>().select(Picking::getOrderTag,Picking::getReview,Picking::getStatus)
                .eq(Picking::getWavePickingCode, wavePickingCode)
                .eq(Picking::getGroupCode,groupCode)
        );
        if (picking == null) {
            throw new ServiceException("波次号【" + wavePickingCode + "】没有查到拣货单信息,请检查波次号是否正确");
        }
        List<Delivery> deliveries =  iDeliveryService.list(new LambdaQueryWrapper<Delivery>()
                .eq(Delivery::getIsCancel, NUM_ZERO)
                .eq(Delivery::getWavePickingCode,wavePickingCode)
                .eq(Delivery::getGroupCode,groupCode));

//        deliveries = deliveries.stream().limit(3).collect(Collectors.toList());
        List<String > deliveryCodes = deliveries.stream().map(Delivery::getCode).collect(Collectors.toList());

        Map<String,List<DeliveryDistribution>> deliveryDistributionMap = iDeliveryDistributionService.list(new LambdaQueryWrapper<DeliveryDistribution>()
                .in(DeliveryDistribution::getDeliveryCode, deliveryCodes)
                .eq(DeliveryDistribution::getGroupCode, groupCode)
        ).stream().collect(Collectors.groupingBy(DeliveryDistribution::getDeliveryItemCode));

        List<DeliveryItem> list = iDeliveryItemService.list(new LambdaQueryWrapper<DeliveryItem>()
                .select(DeliveryItem::getGoodsName, DeliveryItem::getGoodsBarCode, DeliveryItem::getOutboundQuantity, DeliveryItem::getRemarks,DeliveryItem::getCode,DeliveryItem::getDeliveryCode)
                .in(DeliveryItem::getDeliveryCode, deliveryCodes)
                .eq(DeliveryItem::getGroupCode, groupCode));
        list.forEach(e->{
            List<DeliveryDistribution> deliveryDistributionList = MapUtil.get(deliveryDistributionMap, e.getCode(), List.class);
            if(ObjectUtil.isNotNull(deliveryDistributionList)){
                e.setExtendTwo(deliveryDistributionList.get(0).getExtendTwo());
            }
        });
        Map<String, List<DeliveryItem>> deliveryItemMap = list.stream().collect(groupingBy(DeliveryItem::getDeliveryCode));
        deliveries.forEach(e->{
            e.setWarehouseSerialNumber(String.valueOf(warehouseCache.get(e.getWarehouseCode()).get("serialNumber")));
            e.setDeliveryItemList(deliveryItemMap.get(e.getCode()));
        });

        deliveries = deliveries.stream().sorted(Comparator.comparing(Delivery::getOrderNum)).collect(toList());
        return deliveries;
    }

    @Override
    public void updatePrintCountV(String wavePickingCode, String branchCode) {
        baseMapper.updatePrintCount(wavePickingCode,branchCode);
    }

    private List<PickingItemWithRegisterVO> combineGoodsInfoOfSameLocation(List<PickingItemWithRegisterVO> pickingItemWithRegisterVOS) {
        Map<String, List<PickingItemWithRegisterVO>> listMap = pickingItemWithRegisterVOS.stream().collect(Collectors.groupingBy(e -> e.getLocation() + "_" + e.getGoodCode()));
        if (pickingItemWithRegisterVOS.size() == listMap.entrySet().size()) {
            return pickingItemWithRegisterVOS;
        }
        List<PickingItemWithRegisterVO> pickingItemWithRegisterVOList = Lists.newArrayList();
        for (Map.Entry<String, List<PickingItemWithRegisterVO>> entry : listMap.entrySet()) {
            if (entry.getValue().size() > 1) {
                BigDecimal totalDistributionQuantity = entry.getValue().stream().map(PickingItemWithRegisterVO::getDistributionQuantity).reduce(BigDecimal.ZERO, BigDecimal::add);
                BigDecimal totalItemPickingQuantity = entry.getValue().stream().map(PickingItemWithRegisterVO::getItemPickingQuantity).reduce(BigDecimal.ZERO, BigDecimal::add);
                PickingItemWithRegisterVO pickingItemWithRegisterVO = entry.getValue().get(NUM_ZERO);
                pickingItemWithRegisterVO.setDistributionQuantity(totalDistributionQuantity);
                pickingItemWithRegisterVO.setPlanQuantity(totalDistributionQuantity);
                pickingItemWithRegisterVO.setPickingQuantity(totalItemPickingQuantity);
                pickingItemWithRegisterVOList.add(pickingItemWithRegisterVO);
            } else {
                pickingItemWithRegisterVOList.addAll(entry.getValue());
            }
        }
        return pickingItemWithRegisterVOList;
    }
    private static String geneDeliveryOrderNum(String orderNum,BigDecimal unPickingQuantity) {
        return StrUtil.wrap(orderNum, "(", ")") + "-" + unPickingQuantity.intValue() + ", ";


    }

    private String getOutConfirmLockKey(String code) {
        return String.format("outConfirm-lock-%s", code);
    }


}
